Skip to content

fix(chat): finish with length when a Responses upstream reports incomplete reason "length" - #7539

Merged
lidge-jun merged 3 commits into
devfrom
sweep/w1-chat-length
Oct 22, 2026
Merged

lidge-jun merged 3 commits into
devfrom
sweep/w1-chat-length

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

Carries #7392 by @hedging8563 onto the current dev tip and addresses the CHANGES_REQUESTED review.

A Responses upstream that ends a capped turn with status: "incomplete" and incomplete_details.reason: "length" (Volcengine Ark Coding Plan /responses; OpenAI sends max_output_tokens) was classified by the Chat writer as an unsupported boundary. chatCompletionsIncompleteOutcome and responsesJsonToChatCompletion in src/chat/outbound.ts accepted only max_output_tokens and content_filter, so the converter produced upstream stream ended early (length) / upstream_incomplete instead of finish_reason: "length".

  • chatCompletionsIncompleteOutcome now reads the reason through the shared truncationReasonFor table (src/responses/truncated-stop-reason.ts). responsesJsonToChatCompletion calls that function instead of its own two-value check, so the SSE converter, its buffered collector, the direct encoder (src/protocols/encoders/chat.ts) and the JSON projection share one classification.
  • Proxy-synthesized and unknown reasons (adapter_eof, upstream_stall_timeout, missing_terminal_event, max_messages, steered, missing) are not in the table and still fail.

Scope note for reviewers: on current dev, handleChatCompletions over an openai-responses provider already receives the normalized reason, because #7218 made src/server/responses/passthrough-delivery.ts apply rewriteResponsesIncompleteReasonJson to every upstream body. The new handler cases pass with and without this change. The fix therefore makes the Chat writer correct on its own instead of relying on every producer normalizing first; the converter cases are the red→green proof. This matches what normalized producers already deliver (for example pause_turn and refusal map the same way on both sides).

Review follow-ups:

  • The regression moved to tests/responses/chat-incomplete-length-reason.test.ts (the domain the other Chat converter suites use; tests/chat/ does not exist) and is registered in both maps: the new explicitShards file scripts/test-layout/layout-chat.json (listed on the existing shard line of scripts/test-layout/layout.json) and tests/fixtures/test-layout-expected.json. layout.json is at 1999 lines on dev, and an inline entry made it 2000, which the file-size ratchet rejects as NEW_OVERSIZED (first CI run, test 2/4). The shard follows the layout-agent-assertion.json precedent from fix(auth): refuse Codex runtime AgentAssertion auth and scope dual_header assertions to Direct #7470.
  • The owning contracts are updated: structure/transports/responses-wire-shapes.md#adapter-incomplete-reasons now states the Chat writer's vocabulary and failure boundary, and structure/data-planes/inbound-compat.md links "known incomplete limits" to it. The doc stays at its 600-line budget.
  • The negative EOF/stall/missing-terminal cases are kept and extended with max_messages/steered. Explicit streamed-wire assertions were added: the converter's SSE text and the handler's stream:true response must carry exactly one finish_reason: "length" and end with data: [DONE]. The handler also covers SSE and JSON upstreams for both stream:true and stream:false clients.

CodeRabbit reported no actionable comments, and the original PR had no inline review threads.

Not changed here: anthropicIncompleteOutcome in src/claude/outbound.ts still compares against max_output_tokens directly, so the Messages writer still depends on upstream normalization. That writer is outside this Chat lane.

Co-authored-by: hedging8563 45883076+hedging8563@users.noreply.github.com

Verification

Run on base 299e8b68b2 (current origin/dev) with isolated HOME/CLAUDE_CONFIG_DIR/OPENCODEX_HOME (mktemp) and test runner Bun 1.4.3.

  • bun test tests/responses/chat-incomplete-length-reason.test.ts: 9 pass / 0 fail. With src/chat/outbound.ts reverted to dev, 4 converter cases fail and 5 pass (the 5 are the negative case and the 4 handler cases, which pass because of the upstream normalization described above).
  • Focused set (tests/test-layout.test.ts, tests/test-layout-tooling.test.ts, the new file, tests/responses/chat-json-sse-fallback.test.ts, tests/responses/chat-completions-endpoint.test.ts, tests/responses/protocol-direct-encoders-chat.test.ts, tests/responses/incomplete-reason-normalization.test.ts, tests/adapters/bridge-nonstreaming-terminal.test.ts): 412 pass / 0 fail.
  • After the shard move: bun test tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/ci-workflows/file-size-ratchet.test.ts tests/responses/chat-incomplete-length-reason.test.ts: 45 pass / 0 fail; layout.json back at 1999 lines.
  • bun run test:changed (before the JSON-only shard move, which changes no import graph): 16397 pass / 34 skip / 0 fail across 668 files, exit 0.
  • bun run typecheck: exit 0.
  • bun run privacy:scan: passed.
  • bun run structure:check: passed.
  • Windows-sensitive: false. The windows path filter does not match, and no added line under src/ or tests/ contains a marker from scripts/ci/windows-sensitive-diff.sh, so no lane=all dispatch was needed.
  • Not run: full bun run test locally. This Mac is under elevated load and shared with other lanes; the import-graph test:changed set above covers the changed module's dependents, and PR CI covers the Ubuntu suite.
  • No GUI change.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. (No auth, credential, dependency, or workflow changes.)

Summary by CodeRabbit

  • Bug Fixes
    • Responses with an incomplete reason of length now return the Chat Completions finish reason length across streamed and non-streamed responses, including JSON and SSE formats.
    • Responses marked content_filter continue to return that finish reason; unrecognized incomplete reasons remain errors. This keeps Chat completion outcomes consistent across supported response paths.

bylinelog-bot and others added 2 commits October 23, 2026 02:59
…at incomplete vocabulary

Move the regression into tests/responses/ (where the Chat converter suites
live), register it in scripts/test-layout/layout.json and
tests/fixtures/test-layout-expected.json, add explicit streamed SSE wire
assertions and handleChatCompletions cases over an openai-responses
upstream (SSE and JSON, stream true/false), and record in
structure/transports/responses-wire-shapes.md and
structure/data-planes/inbound-compat.md that the Chat writer classifies
through truncationReasonFor while proxy-synthesized reasons stay errors.

Co-authored-by: hedging8563 <45883076+hedging8563@users.noreply.github.com>
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 22, 2026 18:14
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T18:17:03.970517Z 51b17bb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 51b17bb and eb807d7.

📒 Files selected for processing: 2
  • scripts/test-layout/layout-chat.json
  • scripts/test-layout/layout.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

📝 Walkthrough

Chat Completions conversion now classifies incomplete Responses reasons through the shared truncation vocabulary. Recognized reasons, including "length", produce the corresponding finish reason. Unrecognized reasons remain errors. Tests cover streaming, buffered, and JSON conversion paths.

Changes: Chat incomplete-reason mapping

Layer / File(s) Summary
Classify incomplete reasons
src/chat/outbound.ts, structure/data-planes/inbound-compat.md, structure/transports/responses-wire-shapes.md
The streaming and non-streaming conversion paths use truncationReasonFor through chatCompletionsIncompleteOutcome. The documentation describes recognized mappings and error behavior.
Verify conversion outcomes
tests/responses/chat-incomplete-length-reason.test.ts, tests/fixtures/test-layout-expected.json, scripts/test-layout/layout-chat.json, scripts/test-layout/layout.json
Tests cover incomplete-reason outcomes in streamed SSE, buffered SSE, and JSON projections, including handleChatCompletions responses. The test layout assigns the new test to the responses shard.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to eb807

The reviewed changes have no established merge-blocking risk.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: mapping a Responses upstream incomplete reason of "length" to the Chat finish reason "length".
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

… shard

scripts/test-layout/layout.json sits at 1999 lines on dev, so the inline
explicit entry took it to 2000 and failed the file-size ratchet as
NEW_OVERSIZED. Move the entry into a new explicitShards file,
scripts/test-layout/layout-chat.json, listed on the existing shard line.

Co-authored-by: hedging8563 <45883076+hedging8563@users.noreply.github.com>
@lidge-jun
lidge-jun merged commit a0806af into dev Oct 22, 2026
38 of 40 checks passed
@lidge-jun
lidge-jun deleted the sweep/w1-chat-length branch October 22, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants