Ever since Mini and Nano learned to work as a team (see Old Flame, New Love), I'd been feeling safe enough to play the hands-off keeper. Out of sight, out of mind.
I. The Price of Hands-Off Keeping
The automated jobs had been running fine for days, and with the two shrimp keeping an eye on each other, I simply let go for a few days and stopped checking.
Then one morning I casually glanced at Telegram — uh oh.
Both shrimp were out cold — they'd been cut off for two or three days. From howling with hunger at first to barely breathing now. A pitiful sight.
I slammed the table: who did this — who cut off my shrimp's food supply?!
II. Who Moved My Shrimp Food?
The rations I kept for the shrimp came from opencode's Go plan: $10 a month for $60 worth of API credit. For my two street-stall-raised shrimp, Mini and Nano, that was a feast for pennies — they were living large.
The trouble started on September 8th. opencode quietly changed the rules overnight.
It used to be that calling its API just needed an Authorization: Bearer <key>. Now there's a mandatory new header, x-opencode-session — miss it, and you get the cold shoulder in the form of:
HTTP 400: Error from provider (Console Go):
Request is missing x-opencode-session and cannot be routed efficiently.
The complaint letter Nano sent right before passing out spelled it out clearly:
⚠️ Cron 'techub-ops' failed:
HTTP 400: Request is missing x-opencode-session and cannot be routed efficiently.
Mini was no better off — pale as a sheet, hanging by a thread.
I rushed to check opencode's official docs — and sure enough, there it was, the new mandatory header, stated plainly.
A heads-up would've been nice, though. Changing a live protocol in a single changelog entry, with zero announcement? That's just not cool.
III. Roll Call
Angry or not, rescue comes first.
First, figure out exactly who went hungry this time.
Besides my two precious shrimp, Mini and Nano, I also had some scheduled scripts calling model APIs directly.
The culprit, of course, was the new x-opencode-session:
- It's just a client-generated session identifier, an opaque string;
- Keep the same value within a session, and opencode uses it to pin that session's requests to the same backend, so the prompt cache stays hot.
The docs say:
Send a stable session ID in
x-opencode-sessionfor each conversation so we can optimize routing and prompt caching.
But each agent vendor was at a different stage of supporting it. opencode published a list of "verified clients" — Hermes, Claude Code, Codex, Kilo Code, and the like.
Everyone has to buy their own ticket before they can ride.
Official notes here: https://opencode.ai/docs/go/#where-can-i-use-it
On my side, three casualties:
| Task | How it connects | Why it broke |
|---|---|---|
| Scripts calling the API directly | Raw HTTP | No such layer in the code at all — requests naturally carried no session header |
| Nano's ops patrol | Hermes | The current Hermes release (v020) can't send a Session ID |
| Mini's daily jobs | OpenClaw | My OpenClaw doesn't support Session IDs either |
IV. Emergency Rescue
Situation mapped. Time for triage.
First, Order Takeout
If the opencode plan is what's broken, order food from somewhere else to stop the bleeding.
Switch the default provider to OpenRouter and get the dead jobs running again.
Upgrade: Nano Back to Full Health
Nano's case was much simpler. Hermes was on opencode's official "verified clients" list. Hermes v021 already shipped the matching patch — upgrade it, and the problem should go away.
Upgrade, restart, call out to my beloved shrimp. Nano let out a long sigh and slowly came back to life.
Patch: A Temporary Meal Ticket for Mini
Mini was trickier. It didn't support the header yet — I checked the latest release, and there wasn't a trace of x-opencode anywhere in the package.
If the official fix won't come, do it yourself: inject a hardcoded session identifier into the request headers as a stopgap.
In openclaw.json, add a headers declaration to the opencode entry under models.providers:
"headers": {
"x-opencode-session": "hungry-openclaw-********"
}
With the temporary meal ticket in hand, Mini opened its eyes, like waking from another lifetime.
A temporary meal ticket is for emergencies only.
The session ID was only ever a self-assigned client label — as long as it's stable and non-empty, the server accepts it. No issuing authority required.
The cost: a hardcoded value means every session shares one routing identifier, so you lose the cache bonus of pinning each session to its own backend. Hit rates take a hit.
Good enough for now. Wait for the official patch.
Same Prescription, Different Patient
With Mini awake, it was my own scripts' turn.
Same prescription as Mini's: get the scheduled jobs' LLM client its own meal ticket too:
headers = None
if api_base and "opencode.ai" in api_base:
sess = os.getenv("OPENCODE_SESSION_ID") or uuid.uuid4().hex[:12]
headers = {
"x-opencode-session": f"hungry-shrimp-{sess}",
"User-Agent": "opencode-you-own-me-a-meal/1.0",
}
client = OpenAI(api_key=api_key, base_url=api_base, default_headers=headers)
With that fixed, I fired off a request at opencode's front door — and opencode answered like nothing had ever happened.
Done.
V. After the Pain
Both shrimp were rescued without lasting damage, and the jobs were running again.
Still, two or three days of famine left a mark. The scheduled jobs were down for two days — no real harm done, but it stung.
What if, someday, I'm a world-famous guru with earth-shattering services running on my machines? Two days of downtime would be a catastrophe...
ahem... getting ahead of myself...
Either way: don't put all your eggs in one basket.
Two providers on standby, primary-plus-backup routing with automatic failover in the scripts — set it all up now.
Next time some provider tries to pull a fast one on me — bring it on.
Previously:
· Diary of a Shrimp Keeper: My OpenClaw Misadventures
· Diary of a Shrimp Keeper: Old Flame, New Love
Top comments (0)