Fix the 400 MissingSessionID rejections from OpenCode Go inside DeepSeek
Harness (DSH), with a real per-conversation id instead of a static one.
Scope: @deepseek-ai/dsh-llm-pi-ai 0.1.2-rc.1 (as bundled with
@deepseek-ai/dsh; verified 2026-09-07). The patcher verifies every anchor
before writing and fails loudly on layout drift — it never half-applies.
lib/index.js#requestHeaders() gains:
isOpenCodeTarget(model)— provider idopencode-go/opencode, or abaseUrlcontainingopencode.ai;- for those targets only:
x-opencode-session: sessionId ?? "dsh-sessionless", added after the profile headers, so a deployment's static header can never override the per-conversation value; - sessionless callers (rare) send a fixed fallback id, so the header is never absent.
Why this choke point: the Harness already passes a stable per-conversation
session id (agent loop, session titles, compaction all forward it) down to
dsh-llm-pi-ai — but pi-ai only forwards it into generic affinity headers
(x-client-request-id, x-session-affinity), never x-opencode-session,
and only when a provider opts into them. Patching requestHeaders() covers
all three pi-ai API adapters (openai-completions, openai-responses,
anthropic-messages) in one place and survives pi-ai upgrades (it only
touches the DSH package; a DSH upgrade re-runs the patcher).
Run as a user able to write the target files — typically root inside the DSH container:
# A) from the host
docker exec -u root <container> bash apply-opencode-session.sh
# B) from a root shell inside the container
bash apply-opencode-session.sh
# status / rollback
bash apply-opencode-session.sh --check
bash apply-opencode-session.sh --revertThen restart the harness (docker restart <container> or your service
manager) — modules are cached per process, so the patch only loads on
process start.
Default target paths: the DSH install tree and the web profile tree
($DSH_HOME/.dsh/profiles/...). They are hardlinked; the script dedups by
inode, so one write fixes both names. Override with DSH_HOME /
DSH_TARGETS (see the script header).
The patched module lives in the container writable layer: it is lost on
container recreation, image upgrade, or host migration, and it is not
covered by DSH data backups (backups only carry the ~/.dsh volume).
Incident (2026-09-08): a standby DSH restored from a data backup hit
400 MissingSessionID again because the patch lived only in the primary's
container layer.
So keep the patcher in the persistent home volume, next to the other dev patches, and re-apply after every container recreation / DSH upgrade:
# one-time: store the patcher in the persistent volume
cp apply-opencode-session.sh patch-dsh-llm-pi-ai.mjs ~/.dsh/dev-plugins/patches/
# apply / re-apply (idempotent; --check first if unsure)
docker exec -u root -e DSH_HOME=/home/node <container> \
bash /home/node/.dsh/dev-plugins/patches/apply-opencode-session.sh
docker restart <container>Pass
-e DSH_HOME=/home/nodeexplicitly. Inside the container the compose env setsDSH_HOME=/home/node/.dsh, which makes the script's default profile target resolve to a non-existent…/.dsh/.dsh/profiles/...and silently skip it.
Notes:
- The patcher, its anchors check, and the
*.opencode-session-backupfiles survive with the volume; only the patchedlib/index.jsneeds re-applying. - On boot DSH re-syncs the profile-tree copy of
dsh-llm-pi-aifrom the (patched) global module, so after a restart one apply covers both targets (observed 2026-09-08).
| Host | Container | Image | Applied | Entry |
|---|---|---|---|---|
| nas-n100 | deepseek-harness | owen-dsh:0.1.2-rc.1 | 2026-09-07 | intranet http://192.168.23.22:3080 |
| tcloud-01 | deepseek-harness | owen-dsh:0.1.2-rc.1 | 2026-09-08 | https://dsh.app.bulabula.space (basic auth) |
Patcher persisted in ~/.dsh/dev-plugins/patches/ on both nodes (included in
DSH data backups since 2026-09-08).
~/.dsh/settings.yaml:
llm-pi-ai:
providers:
opencode-go:
headers:
x-opencode-session: your-static-iddsh-llm-pi-ai re-reads profiles per request, so this lands on the next request without a restart. Tradeoff: every conversation shares one id — enough to stop the 400s, weaker for OpenCode's prompt-cache routing. Once the patch is applied, the dynamic value wins (see merge order above); remove the static block afterwards to keep the config honest.
- The patch touches only
@deepseek-ai/dsh-llm-pi-ai. It is independent of your@earendil-works/pi-aiversion and of theopenainpm SDK version inside it — any SDK that supportsdefaultHeaders(v4+) forwards the injected header. You do not need any offline upgrade tarballs; everything needed is the two files in this directory. - Your DSH must already ship the opencode-go provider (bundled pi-ai ≥ 0.84). If it does not, upgrade DSH first — that is outside this patch's scope.
- Verified against dsh-llm-pi-ai 0.1.2-rc.1 (2026-09-07). If a future
DSH release changes the layout, the patcher refuses to run ("anchor not
found") instead of half-applying — please open an issue with your
dsh-llm-pi-aiversion (grep '"version"' <path-to>/dsh-llm-pi-ai/package.json) so the new anchors can be added.