Skip to content

fix(openai): send x-opencode-session header to OpenCode Go/Zen - #4191

Closed
yunus25jmi1 wants to merge 1 commit into
docker:mainfrom
yunus25jmi1:fix/opencode-session-header
Closed

fix(openai): send x-opencode-session header to OpenCode Go/Zen#4191
yunus25jmi1 wants to merge 1 commit into
docker:mainfrom
yunus25jmi1:fix/opencode-session-header

Conversation

@yunus25jmi1

Copy link
Copy Markdown
Contributor

Fixes #4164

Problem

OpenCode Go/Zen require an x-opencode-session header ("one stable ID per conversation") and announced that requests without it may error starting 2026-09-06. docker-agent (Cagent/v1.126.0) never sends it, for the built-in opencode-go / opencode-zen aliases or for custom providers pointed at opencode.ai.

Root cause

Headers in the OpenAI provider are static and fixed at client construction (buildHeaderMap in pkg/model/provider/openai/headers.go). There is no per-request hook for OpenCode and no config templating for http_headers, so the only workaround was one hard-coded ID per deployment, which collapses every conversation into a single cache key in serve api / serve chat.

Fix

  • New opencodeSessionMiddleware (pkg/model/provider/openai/opencode.go), registered in NewClient when the provider is opencode-go, opencode-zen, or a custom provider whose base_url host is opencode.ai (suffix-matched; lookalike hosts and path matches are rejected).
  • The value is a salted UUIDv5 derived from the run-loop session ID already on the request context (httpclient.ContextWithSessionID), so every request of one conversation shares it, across processes and in multiplexed server deployments, without leaking the raw session ID to a third party (the codebase deliberately keeps X-Cagent-Session-Id gateway-only).
  • Requests with no session on the context (embeddings, one-off calls) fall back to a per-client UUID, mirroring chatgptAuthMiddleware.
  • A user-set provider_opts.http_headers.x-opencode-session always wins.
  • Docs: short "Session Header" section on both OpenCode provider pages.

Tests

pkg/model/provider/openai/opencode_test.go covers provider detection, stable/opaque ID derivation, one ID per conversation on a shared client, per-client fallback, user override, and no leakage to other providers.

Validation

  • go build ./...
  • go test ./pkg/model/provider/...
  • golangci-lint run (0 issues), go run ./lint . (no offenses)
  • go mod tidy --diff clean
  • Commit is SSH-signed

@yunus25jmi1
yunus25jmi1 requested a review from a team as a code owner September 7, 2026 19:00
@aheritier aheritier added area/docs Documentation changes area/providers/openai For features/issues/fixes related to the usage of OpenAI models kind/fix PR fixes a bug (maps to fix:). Use on PRs only. status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key labels Sep 7, 2026
@aheritier

Copy link
Copy Markdown
Collaborator

👋 Some commits in this PR are not signed and verified by GitHub. Please sign your commits with a GPG or SSH key registered in your GitHub account, then force-push.

Commits that are not verified: b9b59f1

See GitHub's guide on signing commits for setup instructions. I've added status/needs-signed-commits; it will be removed automatically once every commit in this PR carries a valid GitHub-verified signature.

@aheritier

Copy link
Copy Markdown
Collaborator

Work in progress in #4190

OpenCode requires an x-opencode-session header carrying one stable ID
per conversation and rejects requests without it starting 2026-09-06.
docker-agent never sent it: provider headers are static and fixed at
client construction, and there is no config templating for a
per-conversation value, so the only workaround was one hard-coded ID
per deployment.

Add a per-request middleware for the opencode-go / opencode-zen aliases
and any custom provider pointed at opencode.ai. The header value is a
salted UUIDv5 derived from the run-loop session ID already present on
the request context, so every request of a conversation shares it
(including across multiplexed serve api / serve chat deployments)
without leaking the raw session ID to a third party. Requests with no
session on the context fall back to a per-client ID, and a user-set
provider_opts.http_headers value always wins.

Fixes docker#4164
@yunus25jmi1
yunus25jmi1 force-pushed the fix/opencode-session-header branch from b9b59f1 to 161018a Compare September 7, 2026 19:02
@yunus25jmi1

Copy link
Copy Markdown
Contributor Author

Thanks @aheritier, and sorry for the overlap with #4190, I hadn't seen it. Commits are now verified (161018a).

Happy to close this in favour of #4190, but I want to flag one concrete difference first, because it decides whether the header actually delivers "one stable ID per conversation":

#4190 mints the UUID at client construction. That is per conversation in serve api (one runtime per session), but the OpenAI client is built once per process and shared across all conversations in:

  • serve a2a: teamloader.Load once in pkg/a2a/server.go:66, one session.New per A2A context in pkg/a2a/adapter.go:98
  • serve mcp: teamloader.Load once in pkg/mcp/server.go:80/160, one session.New per tool call at :277
  • TUI /new: new session, same team and clients

In those paths every conversation would share a single x-opencode-session, which is the "one ID per deployment" workaround the issue reporter called out as defeating the header's purpose.

This PR instead adds an option.Middleware (same shape as chatgptAuthMiddleware) and derives the value per request from the session ID the run loop already places on the context (httpclient.ContextWithSessionID, pkg/runtime/loop.go:287). Side effects: the ID also survives --resume and server restarts, and the value is a salted UUIDv5 rather than the raw session ID, so it can't be correlated with the local store or the gateway's X-Cagent-Session-Id. Per-client UUID stays as the fallback when no session is on the context.

Whichever you prefer: I can rebase this on top of #4190 as a follow-up that swaps the static header for the middleware, or you're welcome to fold the middleware into #4190 and I'll close this one.

@aheritier aheritier removed the status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key label Sep 7, 2026
@IsmaelMartinez

IsmaelMartinez commented Sep 8, 2026

Copy link
Copy Markdown

Your diagnosis is right, and thanks for flagging it rather than just closing. I checked the paths you named: a2a and mcp both load the team once, so my construction-time UUID collapses to a single ID per deployment there. I had also missed that the session ID is already on the context.

Update: I've pushed your changes into #4190 with you as co-author (c45be640). My static-header version is gone, since it would have won at construction and defeated the per-session derivation. I kept your code as-is apart from softening "requires" and "may be rejected" in the comment and docs, because OpenCode's docs say "send", mention routing as well as caching, and give no date.

Happy to close #4190 if the maintainers would rather just take yours. No hard feelings either way.

@yunus25jmi1

Copy link
Copy Markdown
Contributor Author

Superseded by #4190, which now carries the same middleware-based fix with co-author credit. Closing to keep the review in one place.

@yunus25jmi1 yunus25jmi1 closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation changes area/providers/openai For features/issues/fixes related to the usage of OpenAI models kind/fix PR fixes a bug (maps to fix:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenCode Go/Zen do not send an x-opencode-session header (required by OpenCode from 2026-09-06)

3 participants