fix(claude): decline message threads on translated Messages routes - #5935
kaladinhonor wants to merge 2 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: lidge-jun/opencodex/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (7)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughTranslated Claude Messages and ChangesClaude message-thread handling
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to Translated threaded requests are declined while native passthrough remains unchanged. No actionable merge-blocking risk is established. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Threaded requests on translated routes now fail explicitly rather than proceeding without their earlier context. Direct provider forwarding remains unchanged. The intended automatic client retry has not been independently verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. (4 skipped: 4 unsupported.)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
리뷰 · 우선순위 68 / 80Claude Code를 공식 Anthropic에 붙인 것처럼 켜 두면, 서브에이전트는 두 번째 말부터 대화 전체를 보내지 않습니다. 이 PR은 그 번역 길에서 바탕은 라인 - 라인 - 메인테이너의 판단이 필요한 지점
이 PR은 아직 초안입니다. 댓글을 쓸 때 CI는 대기 중이었습니다. 너의 추천
이 댓글은 grok-bot이 작성했습니다 |
|
✅ Deterministic PR hygiene checks passed. |
✅ READY
Review readiness checklist
✅ 4/4 boxes ticked. This pull request is already Ready for Review. |
|
Thanks for the review. Pushed 98de003:
The PR description is updated with the above. |
Claude Code enables its message-threads beta against first-party Anthropic,
which is what the first-party intercept presents. From a subagent's second
turn it sends `thread: {type: "continue", previous_message_id}` with only the
messages after the anchor and may omit `system` and `tools`, which Anthropic
replays from the stored thread.
The translated path ignored `thread` and translated the delta, so a routed
model received only the latest tool result: no task, no instructions, no
earlier turns, and no error.
Answer a `thread` object on the translated path with a 400 whose
`error.details.error_code` is `thread_unsupported_request`. Claude Code maps
that code to "unsupported", resends the turn with the full conversation and
keeps that model stateless for the session. Native passthrough still
forwards the thread unchanged.
A thread delta omits the system prompt, tools and earlier turns, so counting it undercounts the conversation. Return the same thread_unsupported_request 400 that the translated Messages path returns.
cefdcc3 to
98de003
Compare
) Six focused fixes from the assigned bug batch remain as separate attributed commits. | PR | Change | Author | | --- | --- | --- | | #5969 | Preserve Meta Muse tool-choice semantics and reject unsupported selectors before dispatch. | shawnkim | | #5944 | Remove unsupported hosted web-search declarations for Xiaomi MiMo destinations. | codingbo | | #5938 | Restart the Windows service child after unexpected exits, including exit 0, while reserving the intentional stay-out code. | codingbo | | #5935 | Reject Claude message-thread state on translated routes so the client resends full history. | kaladinhonor | | #5939 | Rewrite standalone `\\0` escapes in Meta tool-schema patterns to equivalent `\\x00`. | boblob6969 | | #5951 | Preserve Kiro-reported credits across stream attempts and in the usage ledger. | codingbo | A separate integration commit keeps upstream-controlled Kiro event-type text out of opt-in debug logs. The Kiro stream retains the previously landed bounded HTTP-error text when combined with credit metering. Left out: #5977. Independent security review found that its local read capability authenticates the request but not the HTTP response. A substituted listener could return a shape-valid forged `protected` verdict. A correct server proof bound to the nonce, endpoint, and body is outside this batch. Both its source commit and status-validation follow-up were reverted in new commits; its test and layout entries are gone. The source PR remains open. Co-authored-by: shawnkim <shawnkim@markncompany.co.kr> Co-authored-by: codingbo <cnsdbo@163.com> Co-authored-by: kaladinhonor <266145786+kaladinhonor@users.noreply.github.com> Co-authored-by: boblob6969 <boblob6969@icloud.com>
Summary
In first-party mode, Claude Code subagents on routed (non-Claude) models lose their task, instructions and history from the second turn onward. The model receives only the latest tool result and typically replies with something like "What would you like me to do?". No error is raised.
Cause
message-threads-2026-08-12) only against first-party Anthropic, and the first-party intercept presents exactly that.thread: {"type": "continue", "previous_message_id": ...}with only the messages after the anchor. It may also omitsystemandtools, because Anthropic replays them from the stored thread.src/server/claude-messages.tsignoredthreadon the translated path and translated the delta as if it were the whole conversation. There is no thread store on that path, so the context was silently lost.Fix
threadobject now gets a 400 whoseerror.details.error_codeisthread_unsupported_request. The request log records the error codeclaude_thread_unsupported.threadto Anthropic.Changes
src/claude/message-threads.ts: detection helper and the error response.src/server/claude-messages.ts: the check, placed right after the native-passthrough return in both/v1/messagesand/v1/messages/count_tokens. Counting a thread delta would undercount the conversation.tests/claude-integration/claude-messages-thread.test.ts: new file, registered inscripts/test-layout/layout.jsonandtests/fixtures/test-layout-expected.json.structure/data-planes/inbound-compat.md: new section "Claude message threads on translated routes".docs-site/src/content/docs/guides/claude-code.md: one paragraph in the first-party section.Where Claude Code reads the code: checked in the Claude Code 2.1.280 binary bundled with Claude Desktop, because the string is not in public docs.
details.error_codefrom the error object (error.errorwhen wrapped), and mapsthread_unsupported_requestto itsunsupported_requestoutcome.message-threadsheader, and marks the agent/model pair unsupported.error.codeis not read on this path. The response keepstype: "invalid_request_error", so any other client still sees an ordinary Anthropic 400.Why
createis refused too: acreateresponse on a translated route would hand Claude Code a message id to continue from. Its nextcontinuecould only be refused as well. Refusing atcreatecosts the same single extra round-trip per subagent. It also keeps Claude Code from holding a thread anchor that no server stores.Observed in practice: on 2.67.0 with first-party mode and a routed
gpt-6-lunasubagent, a subagent asked to read a file and report a secret word forgot the task after the first tool call. With this change applied, the request log shows oneclaude_thread_unsupported400 per subagent. The following turns carry the full history at the configured effort, and the subagent finished the task.Verification
Run on Windows 11 with Bun 1.3.14, with proxy environment variables unset.
bun test tests/claude-integration/claude-messages-thread.test.ts: 2 pass, 0 fail. This coverscontinueandcreate, streaming and non-streaming, count_tokens, the stateless resend and native passthrough.src/server/claude-messages.tschange reverted (plaindev): 1 fail (Expected: 400, Received: 200). The upstream mock received only the orphan tool result.bun run typecheck: pass.bun run structure:check: "structure/ SSOT checks passed".bun run privacy:scan: "Privacy scan passed".bun test tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/ci-workflows/structure-ssot.test.ts: pass.bun test tests/ci-workflows/repo-hygiene.test.ts --timeout 60000: 15 pass. With the default timeout, one devlog scan timed out at 5 s on this machine.bun test tests/ci-workflows/file-size-ratchet.test.ts --timeout 120000: 9 pass.Not run: the full suite. On this Windows machine,
tests/claude-integration/claude-messages-endpoint.test.tsfails on untoucheddevtoo. Its "compatibility is uniform across translated adapters" case times out in a hook withEBUSYon temp removal. The process then keeps the spend-ledger owner (SPEND_LEDGER_OWNER_HOME_CONFLICT), so every later server-starting test in the samebun testprocess fails. This includes the new file when it runs after it. The new file passes on its own and does not touch that path. Full-suite coverage is left to CI.Checklist
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
Required local validation passed; commands, results, and any full-suite exception are documented.
I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.
Summary by CodeRabbit