Skip to content

feat(opencode): forward Claude Code session ID as x-opencode-session - #166

Open
alecchen wants to merge 2 commits into
routatic:mainfrom
alecchen:feat/x-opencode-session
Open

feat(opencode): forward Claude Code session ID as x-opencode-session#166
alecchen wants to merge 2 commits into
routatic:mainfrom
alecchen:feat/x-opencode-session

Conversation

@alecchen

@alecchen alecchen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What

OpenCode Go uses an x-opencode-session header to group requests by conversation, and has said requests without it may error after 09/06. Claude Code 2.1.259 already sends the conversation UUID as x-claude-code-session-id on every /v1/messages request. The proxy was dropping that header. This change forwards it verbatim to OpenCode Go.

The value is stable across turns in one conversation and differs between conversations. It is the same UUID Claude Code uses for its local transcript.

Design

  • Resolve the inbound header once in HandleMessages, right after the X-Request-ID block and before any dispatch. Carry it on context.Context via core.WithSessionID / SessionIDFromContext, so every provider call and every fallback attempt sees the same value.
  • Set x-opencode-session in every OpenCode Go request builder: the provider's executeAnthropic, streamAnthropic, and doRequest (chat and Responses), plus the legacy client's ChatCompletion, SendAnthropicRequest, ResponsesCompletion, and GeminiCompletion.
  • OpenCode Go only. Zen, Bedrock, and OpenRouter are untouched; the legacy client guards on config.ProviderOpenCodeGo.
  • Missing inbound header falls back to a per-request UUID, so the upstream header is always present. No hashing, no proxy state, no mapping.
  • The inbound value is forwarded verbatim, with no truncation or validation.

Tests

  • Provider tests cover the Go chat, anthropic, and responses wire formats, for execute and stream. An empty context omits the header. Zen and Bedrock confirm it stays off those providers.
  • Handler tests cover streaming and non-streaming propagation, same and different inbound sessions, the UUID fallback, and fallback attempts sharing one session. A fake provider confirms the context reaches Stream. A Zen request omits the header end to end.
  • Client tests extend the OpenRouter attribution table with x-opencode-session absent, and check opencode-go sets it while opencode-zen does not.

go test -race ./... passes across all packages. make lint and gofmt are clean.

🤖 Generated with Claude Code

OpenCode Go requires an x-opencode-session header (one stable ID per
conversation) or requests may error after 09/06. Claude Code sends the
conversation UUID as x-claude-code-session-id on every /v1/messages
request; forward it verbatim to OpenCode Go so it can group requests by
conversation.

- Resolve the inbound header once in HandleMessages and carry it via
  context (core.WithSessionID / SessionIDFromContext), so it reaches
  every provider call and every fallback attempt.
- Set x-opencode-session in all OpenCode Go request builders (provider
  executeAnthropic/streamAnthropic/doRequest, plus the legacy client's
  ChatCompletion/SendAnthropicRequest/ResponsesCompletion/
  GeminiCompletion).
- Scope: OpenCode Go only. Zen, Bedrock, and OpenRouter are untouched.
- Missing inbound header falls back to a per-request UUID so the
  upstream header is always present.
- Tests cover streaming + non-streaming propagation, same/different
  session stability, UUID fallback, fallback-attempt sharing, and the
  Zen/Bedrock/OpenRouter scope boundary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alecchen
alecchen marked this pull request as ready for review September 3, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants