Repository navigation
Claude Code: OpenCode Go requests lose session affinity through /v1/messages #3945
Description
Activity
- addedprovider-compatibilityProvider compatibility reportsProvider compatibility reportsproviderProvider adapters, OpenAI-compat presets, upstream API quirksProvider adapters, OpenAI-compat presets, upstream API quirkstoolstool_calls, MCP, web-search / sidecar toolstool_calls, MCP, web-search / sidecar tools
on Sep 7, 2026 리뷰 · 우선순위 76 / 80
설명
이 이슈는 Claude Code가
POST /v1/messages로 OpenCode Go 모델(예:opencode-go/glm-5.3-flash)을 고를 때, 업스트림이 요구하는 대화 고정 헤더x-opencode-session이 빠져 HTTP 400(MissingSessionID/ missing x-opencode-session)이 난다는 제보다. 제보자는metadata.user_id안에 대화 UUID를 넣었고, Chat 쪽 Pi/Go 친화는 이미 #3880으로 고쳤는데, Messages → Responses 리플레이 경로에서는 같은 친화가 안 붙는다고 본다. 재현 스크립트도 loopback 전용·자격증명 미전송으로 깔끔하다. 버전은 설치본 2.46.0 기준이고, 지금devHEAD는6188458ae(package 2.48.0)다.현재
dev코드로 경로를 따라가면 제보가 맞다./v1/messages는src/server/claude-messages.ts의handleClaudeMessages가 Anthropic 요청을 Responses 몸으로 옮긴 뒤handleResponses로 리플레이한다. Go 헤더는src/providers/opencode-go-transport.ts의resolveOpenCodeGoTransport가 붙인다. 조건은 (1) destination registry id가opencode-go, (2) sessionLane 문자열이 있음, (3) provider.headers에 아직x-opencode-session이 없을 때다.src/server/responses/core.ts는 sessionLane을sessionLaneIdFromRequest(req.headers)또는 요청 헤더x-opencode-session에서만 읽는다. Chat Completions(src/server/chat-completions.ts)는 클라이언트x-opencode-session을 그대로 복사하고 #3880이 Pi Chat 레인까지 이어 줬다. Messages 리플레이는 그 복사·합성이 빠져 있다.더 구체적으로,
claude-messages.ts는 리플레이용internalReq헤더를FORWARD_HEADERS(src/adapters/openai-responses.ts)에서만 고른다. 그 목록에는session_id/thread-id/x-codex-parent-thread-id등은 있지만x-opencode-session은 없다. 게다가session_id합성(cacheKeySource === "metadata"일 때prompt_cache_key→ uuid)은nativeRoute(adapter가openai-responses)일 때만 돈다. OpenCode Go로 라우팅되면nativeRoute가 false라서, Claude Code가 준metadata.user_id가 있어도 리플레이 요청에 session lane이 안 생긴다. 그러면resolveOpenCodeGoTransport는 sessionLane 없이 provider를 그대로 돌려보내고, Go는 세션 헤더 없는 요청을 거절한다. 제보의 “replay synthesizes session_id only for native Responses routes / drops explicit x-opencode-session” 설명이 HEAD와 일치한다.왜 지금 점수가 높은가. A/B/C 트랙 문서·config 마감이 tip에 있지만, 실제 Claude Code × OpenCode Go 사용성은 provider-compatibility 구멍이다. #3857/#3880이 Chat 쪽 같은 증상을 이미 닫았고, 이 이슈는 Messages 입구의 형제 구멍이다. 라벨도
provider-compatibility/provider/tools로 맞다. Desktop처럼metadata.user_id가 없는 호출은 헤더리스로 남을 수 있다는 한계도 제보가 스스로 적었고, system-hash fallback을 session id로 쓰면 안 된다는 기존 가드(claude/inbound.ts,claude-messages.ts주석)와도 같은 방향이다.고칠 때는 제보 제안이 대체로 맞다. (1) 명시
x-opencode-session은 Chat처럼 리플레이 헤더로 전달·우선, (2) 없을 때만metadata.user_id기반(이미 있는prompt_cache_key/conversationIdFromClaudeMetadata계열)으로 lane을 만들어resolveOpenCodeGoTransport에 넘기고, (3) system-hash cohort는 절대 session으로 쓰지 않으며, (4) native Anthropic passthrough는 그대로 둔다.registryEntryForProviderDestination(...)?.id === "opencode-go"검사는 transport에 이미 있다. wire override 앞뒤로 destination id가 깨지지 않는지만 회귀 테스트로 잠그면 된다. 테스트는 제보대로 Claude handler + fake outbound로 실제 upstream Go 헤더를 보면 충분하다.src/server/claude-messages.ts (nativeRoute 가드 안 session_id 합성) - Go 등 non-native 라우트에서는 metadata 기반 session lane이 안 생겨 resolveOpenCodeGoTransport가 빈손이다.
src/adapters/openai-responses.ts FORWARD_HEADERS -x-opencode-session이 없어 클라이언트가 명시한 Go 세션 헤더가 리플레이 Request로 전달되지 않는다.
src/server/responses/core.ts resolveOpenCodeGoTransport 호출부 - lane 입력이 헤더뿐이라 Messages 리플레이가 메타데이터를 안 실어 주면 Go 친화가 무조건 스킵된다.
src/providers/opencode-go-transport.ts - 로직 자체는 건전하다. 문제는 호출 측이 sessionLane을 안 주는 것. 커스텀 URL lookalike는 registry id 검사로 이미 제외된다.
재현·범위 - 2.46.0 실측과 HEAD 정적 경로가 같고, #3880 Chat 수정 후에도 Messages 구멍은 남아 있다. Desktop(메타데이터 없음)은 별 이슈로 남겨도 된다.메인테이너의 판단이 필요한 지점
- Messages 리플레이에서 lane 소스를
session_id합성으로 통일할지, Go 전용으로x-opencode-session만 붙일지 FORWARD_HEADERS에x-opencode-session을 넣어 전역 전달할지, Claude 리플레이 블록에서만 복사할지- Desktop/헤더리스 Go 호출을 같은 PR에서 완화할지, 이번엔 Claude Code metadata 경로만 닫을지
- 기여자 PR을 기다릴지, maintainer가 fix: preserve Pi session affinity through native Chat #3880 후속으로 바로 집을지
너의 추천
열어 두고 help wanted(또는 maintainer 단기 픽스)로 받는 게 맞다. 최소 수정은 Claude 리플레이에서 (a) 수신
x-opencode-session전달, (b) 없고cacheKeySource === "metadata"일 때 lane을 만들어resolveOpenCodeGoTransport가 돌게 하는 것. system-hash는 절대 lane으로 쓰지 말 것. #3880과 같은 계열 회귀 테스트를 Messages 경로에 추가하고, 통과하면dev에 넣자. types/config 대분할과 무관하다.이 댓글은 grok-bot이 작성했습니다
- Messages 리플레이에서 lane 소스를
Fixed on dev by #3961 (60bcb90). Claude requests retain canonical Go session affinity, including explicit headers and metadata fallback; invalid/empty identities do not suppress usable fallback or create shared affinity. Both wires are covered by current-head CI34168444860. Callers without any usable per-conversation identity still need to supply one; no global identity is synthesized.
- added a commit that references this issue
on Sep 10, 2026 - added a commit that references this issue
on Sep 17, 2026
Note
This was entirely done by GPT/Claude. - David
Client or integration
Claude Code
Provider or upstream service
OpenCode Go (
opencode-go)OpenCodex version
2.46.0; Claude Code 2.1.263. The missing Messages forwarding/fallback remains in the reviewed
devsource at 76826fe.Endpoint or capability
POST /v1/messages: Claude conversation affinity during replay into the Responses routing stack and Go transport.Current behaviour
A real Claude Code request selecting
opencode-go/glm-5.3-flashfailed with Go's missing-session error. The request includes per-conversation identity inmetadata.user_id, but replay synthesizessession_idonly for native Responses routes. It also drops an explicitly suppliedx-opencode-sessionheader.Expected behaviour
Go should receive stable opaque per-conversation affinity. Explicit session lanes and configured provider headers must retain precedence; conversations must never share an identifier derived from common system text.
Minimal redacted request or reproduction
Configure Go with its key in the local proxy. Save this dependency-free script as
reproduce.py:Run:
It sends an Anthropic-shaped Messages request with a Go model, a short user message, and
metadata.user_idcontaining a new conversation UUID. It never reads or sends credentials.--conversation UUIDreuses a conversation;--toolrequests a harmless tool call without executing it.Alternatively, reproduce through the configured
ocx-claudelauncher itself. Save this second version asreproduce-claude.py:Run:
This variant invokes the real Claude Code client through
ocx-claude, fixes the Go model, disables built-in tools and MCP servers, and captures diagnostics without printing them. A fresh run against the working patched local OpenCodex 2.46.0 workaround produced:Script exit status:
0. This confirms the patched local client path only; it does not establish pristine or refined-branch runtime behavior.Actual response or error
The prepatch Claude Code call failed with
MissingSessionID/ "Request is missing x-opencode-session and cannot be routed efficiently" (400). After the metadata-affinity workaround, Claude Code returnedGO_OK.A fresh standalone run of the command above against the patched local OpenCodex 2.46.0 workaround produced:
Exit status:
0. This result applies only to the patched local 2.46.0 workaround.For comparison, the same standalone script was run once against an isolated server from the unmodified npm
@bitkyc08/opencodex@2.46.0tarball, after verifying its published integrity. The temporary server used fresh homes, no native subscription credentials, and no native passthrough:Observed output:
Exit status:
1. The error line is normalized by the reproducer, not a verbatim raw response body. The temporary server was stopped after the probe; existing proxies were untouched. Only the standalone HTTP reproducer was run against pristine 2.46.0. The directocx-claudevariant above was tested against the patched workaround only.Upstream documentation
OpenCode Go: https://opencode.ai/docs/go/
Prior requirement and ingress fixes:
#3344
#3378
#3405
#3857
#3880
Suggested mapping or implementation notes
Match
registryEntryForProviderDestination(route.provider)?.idbefore wire overrides, covering renamed canonical destinations without including custom/lookalike URLs. Forwardx-opencode-sessionitself, matching Chat ingress. Synthesize the existing metadata-derived UUID only when no explicit session lane or Go header exists. Keep the metadata-only guard; shared system hashes never become session IDs. Native Anthropic passthrough remains unchanged.Additional context and attachments
The proposed branch targets
dev. Regression tests call the complete Claude handler with fake outbound fetches and inspect actual upstream Go headers. The separately installed 2.46.0 workaround passed real Claude/Go calls and Go tool-call probes through Messages and Responses; this runtime evidence is distinct from validation of the refined branch.Desktop requests lacking metadata and explicit conversation identity remain headerless and may still fail on Go. Calls sharing one Claude session's metadata share its affinity.
Checks
Co-authored-by: GPT-6 Astra noreply@openai.com
Co-authored-by: Claude Fable 5.1 noreply@anthropic.com