Repository navigation
Description
Env: 9Router v0.5.104 (decolua/9router@87a74e1), OpenCode client on Windows (win32, pwsh), opencode free tier (/inference/v1). Also reproducible by POSTing to /v1/chat/completions directly — no client involved.
Problem: open-sse/utils/opencodeFingerprint.js treats a caller tool as "conflicting" whenever its description text differs from the frozen fingerprint definition, then works around it by renaming the caller's tool and appending a placeholder:
shell→shell_client, alias map recorded, restored on the response side- a placeholder
shellappended Do not call the \shell` tool; it is a placeholder that does nothing. Use the other tools you have been given instead.` injected into the system prompt
Two consequences:
1. The most common case is misclassified. OpenCode composes the shell description from process.platform:
// OpenCode, packages/core/src/tool/plugin/shell.ts:28-46
const OS = process.platform === "darwin" ? "macOS"
: process.platform === "win32" ? "Windows"
: process.platform === "linux" ? "Linux" : process.platform
const description = (shell) => [
"Execute a shell command and return its output.",
...(shell ? [`Commands run on ${OS} using ${shell}.`] : []),
"Quote file paths containing spaces or special characters.", ...
].join(" ")The captured fingerprint says Commands run on Linux using bash. (captured on Linux). The same tool on Windows says Commands run on Windows using pwsh. — a 2-character difference inside a 533-character string, but isFingerprintDefinition() compares with === / JSON.stringify, so every request from a non-Linux OpenCode client takes the alias path. read is unaffected: its description is a plain literal on every platform, so it still matches byte-for-byte and is passed straight through.
2. The alias name is derived from the caller's tool list, so it collides. concealFingerprintToolNames() builds taken from every caller tool name, then bumps \${key}_client`→_client2→_client3. A client that already declares shell_client/shell_client2(a plugin, a harness shim) pushes the alias toshell_client3`. The model then sees four shell-ish names, and a call to one of them can reach the client as a tool it does not have.
Measured against this gateway — a stock OpenCode-shaped body vs. one that also declares the two plugin names:
caller tools |
names the model actually receives |
|---|---|
read (exact), shell (Windows/pwsh) |
read, shell_client, shell |
read, shell, shell_client, shell_client2 |
read, shell_client3, shell_client, shell_client2, shell |
Both get the placeholder sentence; it is singular (the \shell` tool) because only shell` was appended.
Repro:
- POST
{ model: "<any opencode free model>", messages: [{role:"user",content:"List every tool name you were given, in order."}], tools: [...] }to/v1/chat/completions, with the tool arrays from the table above. - The reply lists
shell_client/shell_client3and quotes the placeholder sentence verbatim.
Why it matters: the gate needs OpenCode's exact read/shell definitions on the wire — it does not need the caller's tool to be renamed. When the caller's tool already takes the same arguments (the real OpenCode shell has command/workdir/timeout/background in the same key order as the fingerprint), the definition can simply be adopted in place. That removes the alias, the alias map, the response-side restore, the collision-only alias numbering, and the placeholder sentence — for exactly the clients that never needed any of it.
Fix: #4972 — normalize the fingerprint definition in place when the caller's parameter names can carry it, and keep the alias + restore path only for genuinely different schemas (already covered by the existing tests).
Related: #4842 (v2 /inference/v1 fingerprint), #4132 (zen mirror).
Activity
JustSEMI commentedon Oct 16, 2026
same issue lol, using opencode CLI
victor-petropoulos commentedon Oct 17, 2026
Confirming this on macOS (darwin, OpenChamber-managed OpenCode server v2.0.29), no Windows involved.
OpenCode composes the description as
Commands run on macOS using ${shell}., so the mismatch fires for every request from this client too — sameisFingerprintDefinition()===path, sameconcealFingerprintToolNames()rename. What surfaced it as a tooling outage rather than a prompt artifact: subagents read their own catalogs and found adefault.shellplaceholder (no-op, as your note describes) alongside a functionaldefault.shell_client, and reportedNo tool named "shell_client" is currently availablewhen the catalog was rebuilt without the gateway-injected entry.Three independent subagent catalog reads, verbatim, all consistent with your alias path:
default.shell— described in-catalog as a placeholder that does nothing.default.shell_client— functional; 10/10 controlledechoprobe calls succeeded when present.Two extra data points worth adding:
default.shellget silence, which reads exactly like a dead shell. That is likely why some of these reports looked like infrastructure flakiness rather than a rewrite.shell_clientand the response-side restore is per-request, any harness that persists tool names across requests (or across the gateway's own stateless boundary) sees names that do not match what it registered. Filed the host-side view as Subagent shell tool (shell_client) intermittently absent from catalog; placeholder default.shell twin confuses openchamber/openchamber#5050; triage there correctly redirected to OpenCode, but the registrar is this pass.Nothing here contradicts the diagnosis or #4972 — it just adds a second non-Linux platform and the operational symptom (agents reporting a missing tool rather than a renamed one). Happy to provide session transcripts or re-run instrumented probes against the fix.
BIDZZ-RAWR commentedon Oct 17, 2026
Adding a Linux + zsh data point with the same operational symptoms (OpenCode v2.0.31 beta, Arch Linux x64,
$SHELL=/usr/bin/zsh, self-hosted relay using@ai-sdk/openai-compatiblewith 9Router in the serving chain).Commands run on Linux using zsh.while the frozen definition saysbash, so the===/isFingerprintDefinition()path triggers on every request.shell_client({command, workdir})calls sometimes succeed, most fail withNo tool named "shell_client" is currently available. Please use a tool from the available tool list.No tool named "execute"for Code Mode, leaving only file tools (read/grep/glob) usable. Fresh sessions created afterwards were healthy again.shelltool; it is a placeholder that does nothing” system note reached every agent (main and subagents) and actively discouraged the only working path (shell), while models kept calling the renamedshell_client. Agents self-correct toshellonly after being told explicitly.Local workarounds that work while #4972 ships:
shell_client/bashas real tools, invoked via Code Mode (execute→tools.bash({command, workdir})) — reliable in healthy sessions.shelldirectly (as suggested in the OpenCode-side issue) — models comply after one explicit note.Can provide redacted transcripts/logs and re-test against the fix when it lands.
BlackCatOfficialytb commentedon Oct 19, 2026
mostafa-binesh commentedon Oct 19, 2026
You have to create a new session after installing the new version
fix(opencode): send the CLI env marker and accept OS shell wordings
fix(opencode): send the CLI env marker and accept OS shell wordings
fix(opencode): send the CLI env marker and accept OS shell wordings
fix(opencode): normalize colliding fingerprint tool definitions in place
# v0.5.111 (2026-10-26)
fix(opencode): normalize colliding fingerprint tool definitions in place
fix(opencode): normalize colliding fingerprint tool definitions in place
fix(opencode): normalize colliding fingerprint tool definitions in place
# v0.5.111 (2026-10-26)
fix(opencode): normalize colliding fingerprint tool definitions in place
- **Ollama**: add `ollama-local` TTS provider serving the Phonon fami…
- **Ollama**: add `ollama-local` TTS provider serving the Phonon fami…
fix(opencode): normalize colliding fingerprint tool definitions in place
# v0.5.111 (2026-10-26)
fix(opencode): normalize colliding fingerprint tool definitions in place
fix(opencode): normalize colliding fingerprint tool definitions in place
- **Ollama**: add `ollama-local` TTS provider serving the Phonon fami…