Skip to main content
kRouter
All posts
Fix an error

OpenCode MissingSessionID: the x-opencode-session fix

OpenCode Go now returns HTTP 400 MissingSessionID for any request without an x-opencode-session header. What changed, why proxies broke, and the fix.

Kodelyth · The team behind kRouter
· Updated
7 min read

If you use OpenCode Go through any kind of proxy, gateway, or router, you may have woken up to every single request failing with the same wall of red:

HTTP 400: {
  "type": "error",
  "error": {
    "type": "MissingSessionID",
    "message": "Error from provider (Console Go): Request is missing x-opencode-session and cannot be routed efficiently."
  }
}

Nothing about your setup changed. Your API key is fine. Your subscription is active. Every model under ocg/ — glm-5.2, kimi-k3, minimax-m3, all of them — fails identically.

This is a provider-side change, and it is worth understanding properly, because the same class of change will happen again with a different provider.

What actually changed

OpenCode began requiring an x-opencode-session header on every request to the Go endpoint. Requests without it are rejected before they reach a model.

The header was not new. It has been part of the OpenCode client's behaviour for a while — the official client sends one, so the official client never noticed. What changed is that the header went from optional to mandatory. The moment enforcement switched on, every integration that had not been sending it broke at once.

That is the important detail for anyone debugging this: there is no gradual failure signal. It is not a rate limit that ramps, or a model that degrades. It is a hard 400 on the first request after enforcement began, and on every request after that.

Why the error message is a little misleading

The message says the request "cannot be routed efficiently", which reads like a performance warning rather than a hard requirement. It is not a warning. The request is refused.

The word "efficiently" is a clue to the purpose of the header, though, and it is worth knowing because it tells you what a correct value looks like.

What the header is actually for

x-opencode-session identifies a conversation. It is not authentication — your API key already does that. It is a routing and caching hint.

When a provider knows that twenty requests belong to one ongoing conversation, it can:

  • route them to the same backend node, so the KV cache for that conversation stays warm
  • avoid re-processing a long prompt prefix it has already seen
  • attribute usage coherently across a multi-turn session

That is why the value has to be stable across the turns of one conversation and different between separate conversations. A random value per request satisfies the presence check and defeats the entire purpose — you get the header accepted and the cache benefit thrown away.

The value itself is opaque. OpenCode does not parse meaning out of it. The shape the official tooling uses is ses_ followed by 32 lowercase hex characters.

Fixing it in kRouter

If you are on kRouter, upgrade:

npm i -g @sifxprime/krouter@latest

Anything from v0.5.151 onward sends the header. Nothing else to configure, no new setting, no key to paste.

A few details of how it behaves, because they matter if you are debugging:

  • The value is stable for the life of a conversation, so OpenCode's routing and prompt caching actually work rather than merely being satisfied.
  • If your client already speaks OpenCode's protocol and sends its own x-opencode-session, kRouter passes yours through untouched. Overwriting it would split one conversation into two upstream sessions and lose exactly the cache locality the header exists to provide.
  • The value is namespaced per client tool, so two different tools that both call their thread "1" do not collide into a single upstream session.
  • It is applied to every OpenCode Go transport — the OpenAI-style /chat/completions endpoint, the Anthropic-style /messages endpoint that the MiniMax models use, and, since v0.5.163, the /responses endpoint described below.

Fixing it in your own client

If you are talking to OpenCode Go directly rather than through a router, add the header yourself.

const SESSION_PREFIX = "ses_";
 
// Derive a stable id from something that identifies the conversation — a thread id,
// a chat id, whatever your app already has. Hashing keeps it opaque and fixed-width.
function sessionIdFor(conversationId) {
  const digest = crypto
    .createHash("sha256")
    .update(String(conversationId))
    .digest("hex")
    .slice(0, 32);
  return SESSION_PREFIX + digest;
}
 
await fetch("https://opencode.ai/zen/go/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${apiKey}`,
    "x-opencode-session": sessionIdFor(thread.id),
  },
  body: JSON.stringify({ model: "glm-5.2", messages, stream: true }),
});

Three things to get right:

  1. Same conversation, same value. Derive it from a conversation identifier, not from Date.now() or a fresh UUID per call.
  2. Different conversations, different values. If every user shares one hardcoded string, you have handed the provider a single enormous session and the cache behaviour will be worse than sending nothing coherent at all.
  3. Do not put anything sensitive in it. It is a header sent on every request. Hash whatever you derive it from rather than passing a raw email, user id, or path.

A second OpenCode Go failure: Responses-only models (v0.5.163)

The session header was not the last contract change. OpenCode serves seven Go models only on its Responses API: grok-4.5, grok-4.6, grok-4.7, gpt-5.6-luna, gpt-6-luna, muse-spark-1.2-contributor and muse-spark-1.3-contributor. Before v0.5.163, kRouter sent them to /chat/completions, and OpenCode answered ModelProtocolUnsupported.

The worse part was the status code. OpenCode reports that error as HTTP 401, and a 401 reads like a bad credential, so kRouter put the whole OpenCode Go account on cooldown. One request to a Grok model broke every other model on the same account until the cooldown ran out.

v0.5.163 fixes both:

  • The seven models go to OpenCode's /responses endpoint, and kRouter converts to and from the Responses API itself, so Claude Code, Codex and plain OpenAI clients all work with them.
  • The "not supported" error is reported as a 400, so it no longer cools down the account.
  • Every model on the OpenCode Go provider page now has a Protocol control (Auto, Chat Completions, Messages or Responses). If OpenCode moves a model to a different endpoint again, you can follow it the same day instead of waiting for a release.

The same release also filled in the model list: 43 OpenCode Go models, up from 11. The three-protocols guide covers which model uses which endpoint and how to pick one.

What to check if it still fails after the fix

If you are on a current version and still seeing MissingSessionID, work through these in order:

  • Confirm the version actually running. krouter --version should report 0.5.151 or newer. A globally installed CLI and a locally installed one can disagree; npm i @sifxprime/krouter without -g installs into the current folder and does not update the krouter on your PATH.
  • Check which endpoint is failing. If only one model fails and the rest work, the problem is more likely to be that model's transport than the header.
  • Look at the actual outgoing request, not the config. A proxy in front of your router can strip unknown headers; that is uncommon but it does happen with corporate egress proxies.

The broader lesson

A provider turned an optional header into a required one with no deprecation window, and then the same provider moved seven models to a different endpoint. It will not be the last time.

If you maintain an integration against a provider API, the defensive posture is:

  • Send what the official client sends, even when the API does not currently require it. The official client is the de facto specification, and anything it sends is a candidate for becoming mandatory.
  • Treat a 400 with an unfamiliar error type as a contract change, not as a bug in your request construction. MissingSessionID is not a validation error about your payload; it is the provider telling you the contract moved.
  • Do not assume silence means stability. A header that has been optional for a year can become required on a Tuesday.

Common questions

Is MissingSessionID a problem with my API key?

No. Authentication is handled by your Authorization header, and an auth failure returns 401 or 403, not 400. MissingSessionID means the request authenticated correctly and was then rejected for a missing routing header.

Can I just send a random value for x-opencode-session?

It will pass the presence check, but you should not. The header exists so the provider can route a conversation's turns to the same warm cache. A fresh value per request makes every turn look like a brand-new conversation, so you lose prompt-cache reuse and pay full price for the prefix each time.

Does this affect every OpenCode Go model?

Yes. The check happens before model routing, so glm-5.2, kimi-k3, kimi-k2.7-code, minimax-m3 and the rest all return the same 400.

Which kRouter version fixes it?

v0.5.151 and newer send the session header. v0.5.163 also fixes the seven Responses-only models and stops their "not supported" error from cooling down the account. Upgrade with npm i -g @sifxprime/krouter@latest.

Does kRouter overwrite a session header I send myself?

No. If your client sends a valid x-opencode-session, kRouter forwards it unchanged and does not substitute its own.

Why did one Grok request break every other OpenCode Go model?

Before v0.5.163, a Responses-only model sent to the wrong endpoint got ModelProtocolUnsupported back as HTTP 401. kRouter took the 401 as a bad account and cooled the whole account down. From v0.5.163 the request goes to the right endpoint, and the error, if it happens, is a 400 that does not touch the account.

Kodelyth · The team behind kRouter

Published by Kodelyth, the team that builds kRouter. Posts are drafted with AI assistance and reviewed by a person before they go out. kRouter is free and MIT licensed.

Install kRouter