You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
shell tool description names the backend shell, so models call a non-existent bash tool #57656
Models routinely call a tool named bash. This build registers only shell, so the call fails immediately:
No tool named "bash" is currently available. Please use a tool from the available tool list.
The model then retries with shell and the command runs. Cost: one wasted tool call, and an error surfaced to the user mid-task.
Why the model does it
Ranked, from the actual signals a model reads:
The shell tool's own description uses "bash" as the verb. On Windows the description reads:
Commands run on Windows using bash.
This text is read at tool-selection time on every turn, and it names the interpreter rather than the tool. It is the strongest single cause.
V2 documents bash as an alias of shell (docs: "shell tool named shell (bash kept as alias)"), so bash looks like a legitimate name in this ecosystem.
Model prior: nearly every other coding agent names its command tool bash.
What this is not
Not a relay/proxy renaming tools. This is not the shell_client pattern from #56193 / #56794 - nothing rewrites the tool list here; the model reads the description above and types bash itself. Related, though: in #56193 a maintainer confirmed the description embeds the backend shell name (there, "using powershell"), the same text-vs-registration mismatch.
Reproduction
Windows, opencode.json with "shell": "C:\Program Files\Git\bin\bash.exe".
Ask the agent to run a shell command in a fresh session.
Observe the tool-selection step: the model emits bash a percentage of the time (intermittent, model-dependent).
The call fails with No tool named "bash" is currently available; the retry with shell succeeds.
Suggested fix (either is enough)
Expose bash as a real entry in the tool catalogue (an alias of shell), so the name the description advertises actually resolves; or
Reword the description so the tool name is unambiguous, e.g. Run a shell command (this tool is named "shell"; it executes via bash on Windows).
Environment
opencode v2.0.32 (bundled with OpenChamber 2.0.1)
Windows 11, Git Bash at C:\Program Files\Git\bin\bash.exe
opencode.json shell field points at that bash.exe
Plugins cannot work around it: the V2 plugin API only exposes ctx.tool.hook("execute.before", event); there is no tool-registration or tool-rename hook.
To restate the request: models sometimes call a bash tool because the shell tool's description names the interpreter ("Commands run on Windows using bash."). You'd like either a real bash alias for shell, or wording that makes the tool's name clear. As it stands this is working as designed: the tool is registered as shell, the description correctly names the configured shell, and the model recovers after the error. So I'm treating this as an improvement request, and maintainers will consider it.
You can get rid of the wasted call today with a plugin. The execute.before hook can change event.tool, and OpenCode looks up the tool from that field after the hook runs, so a bash call can be redirected to shell. It then goes through shell's normal permission checks:
// .opencode/plugins/bash-alias.ts (or ~/.config/opencode/plugins/bash-alias.ts for all projects)import{Plugin}from"@opencode/plugin"exportdefaultPlugin.define({id: "bash-alias",asyncsetup(ctx){awaitctx.tool.hook("execute.before",(event)=>{if(event.tool==="bash")event.tool="shell"})},})
Files in .opencode/plugins/ are loaded automatically. You can also point to the file from plugins in opencode.json. I haven't run this inside OpenChamber, so let us know if it doesn't load there.
Confirmed working inside a third-party desktop harness, so here are the details the issue asked for.
Environment: opencode v2.0.32 bundled with OpenChamber 2.0.1, Windows 11, opencode.json has "shell": "C:\Program Files\Git\bin\bash.exe". The shell tool description in this build reads exactly: Commands run on Windows using bash.
The event.tool redirect works — and the plugin loads in this harness. A local plugin in the global plugin dir (~/.config/opencode/plugins/bash-alias.ts) is picked up by the config watcher, confirmed in the server log:
One correction for anyone copying the snippet: the Plugin.define({ ... }) form from the comment above did not match what this build actually loads from plugins/*.ts. The working shape here is the plain object export, same as other local plugins:
(ctx.tool.hook("execute.before", …) with a mutable event.tool is confirmed real in this build — another local plugin already reads event.tool and mutates event.input.command the same way.)
Since it sits in the global plugin directory it applies to every project, not just one.
One thing worth considering upstream: the model-side cost is not just one wasted call. When the harness surfaces the failure as a user-visible banner mid-task, the user sees an error and has to decide whether the task is still running. Two cheap fixes would remove that entirely — register bash as a real catalogue alias of shell, or keep the backend name out of the description, e.g. Run a shell command (this tool is named "shell"; on Windows it executes via bash).
Still present on v2.0.32, and it is generated per-request — here is where
Follow-up with the details, now that the event.tool redirect is confirmed working.
1. Reproduced today on v2.0.32. Windows 11, "shell": "C:\\Program Files\\Git\\bin\\bash.exe", opencode --version → 2.0.32 (desktop harness). The shell tool's description still reads, verbatim: Commands run on Windows using bash.
2. The source, so nobody has to hunt for it.packages/core/src/tool/plugin/shell.ts on dev:
constdescription=(shell?: string)=>["Execute a shell command and return its output.",
...(shell ? [`Commands run on ${OS} using ${shell}.`] : []),
...
].join(" ")
So ${shell} is the configured backend shell's name, interpolated straight into the text a model reads at tool-selection time. Two consequences:
It is not a stale constant. Grepping the binary for that sentence finds nothing — it only exists after concatenation, so fixing strings/docs cannot touch it. The template is the bug.
It is not specific to bash. Whatever shell is configured gets advertised as if it were a tool name: zsh, pwsh, fish become equally plausible emissions. bash is merely the likeliest, because it also matches the dominant model prior.
3. This is the text/schema mismatch #56193 hit from the outside. That thread closed as not-planned against 9Router (it renamed shell → shell_client because the description differed from its saved copy), and the reasoning there restates the mechanism: "shell is the only shell tool it registers, and the description changes with the platform". The rename was 9Router's, but the trigger was this sentence advertising a name that is not in the catalogue.
4. The cost is not always one call. When the model recovers it is one wasted call plus a user-visible error banner mid-task. #57584 tracks a case where the emission persists over many turns — different cause per that thread (model/history-driven, not descriptive), same failure mode, unbounded cost once the pattern is in the history. The execute.before plugin removes the symptom for users who know the bug exists and can write TypeScript; a default install has nothing.
5. Minimal fix that keeps the information. The catalogue already carries the tool's name, so the sentence need not introduce a second candidate noun — either of:
...(shell ? [`On ${OS} it executes via ${shell}.`] : []),
...(shell ? [`This tool is named "shell"; on ${OS} it executes via ${shell}.`] : []),
The alternative — registering bash as a real catalogue alias of shell — fixes the recovery path rather than the emission, which is why I still prefer the wording change. Happy to send a one-line PR for either wording; it changes no behaviour, only the string. Also happy to test a build from a branch in this harness if that helps.
Independent reproduction on Linux 2.1.4, with no Windows bash.exe and no shell override. Same symptom, but a different source of the prior, so it is not only the description text.
Environment: opencode 2.1.4, Ubuntu 24.04 x86_64, zsh, tmux. Plugins: superpowers, @tarquinen/[email protected]. No shell setting in opencode.jsonc.
Evidence from ~/.local/share/opencode/opencode.db on this machine:
23,409 completed bash calls from the V1 era vs 2,974 shell under V2. The models here ran under V1 for months, so bash is a strong prior even when the tool description never says "bash".
Since upgrading to V2 (2026-10-26): 789 successful shell calls and 3 rejected bash calls.
All three were in one 4.5-hour session (agent architect, model deepseek-v4-pro), and each failing assistant message also carried a valid parallel call (read), e.g.
The same session had successful shell calls before and after, so this is intermittent model drift, not a permission rule or a restricted toolset.
The alias (or the execute.before redirect — thanks for that) is needed for V1-history models too, not just the Windows description case. A dispatch-level alias map would also cover the other renamed V1 names if a model emits them (task → subagent).
Activity
opencode-agent commentedon Oct 21, 2026
Thanks for the detailed write-up!
To restate the request: models sometimes call a
bashtool because theshelltool's description names the interpreter ("Commands run on Windows using bash."). You'd like either a realbashalias forshell, or wording that makes the tool's name clear. As it stands this is working as designed: the tool is registered asshell, the description correctly names the configured shell, and the model recovers after the error. So I'm treating this as an improvement request, and maintainers will consider it.You can get rid of the wasted call today with a plugin. The
execute.beforehook can changeevent.tool, and OpenCode looks up the tool from that field after the hook runs, so abashcall can be redirected toshell. It then goes throughshell's normal permission checks:Files in
.opencode/plugins/are loaded automatically. You can also point to the file frompluginsinopencode.json. I haven't run this inside OpenChamber, so let us know if it doesn't load there.Related issues:
bashafter heavyWrite-Outputuse; intendedread/edit/globcalls emitted asbash Write-Output ...(mimo-v2.6-pro via opencode-go) #57584: a model getting stuck emittingbashtool callsshell_client)weierliteyangming-hue commentedon Oct 21, 2026
Confirmed working inside a third-party desktop harness, so here are the details the issue asked for.
Environment: opencode v2.0.32 bundled with OpenChamber 2.0.1, Windows 11,
opencode.jsonhas"shell": "C:\Program Files\Git\bin\bash.exe". Theshelltool description in this build reads exactly:Commands run on Windows using bash.The
event.toolredirect works — and the plugin loads in this harness. A local plugin in the global plugin dir (~/.config/opencode/plugins/bash-alias.ts) is picked up by the config watcher, confirmed in the server log:One correction for anyone copying the snippet: the
Plugin.define({ ... })form from the comment above did not match what this build actually loads fromplugins/*.ts. The working shape here is the plain object export, same as other local plugins:(
ctx.tool.hook("execute.before", …)with a mutableevent.toolis confirmed real in this build — another local plugin already readsevent.tooland mutatesevent.input.commandthe same way.)Since it sits in the global plugin directory it applies to every project, not just one.
One thing worth considering upstream: the model-side cost is not just one wasted call. When the harness surfaces the failure as a user-visible banner mid-task, the user sees an error and has to decide whether the task is still running. Two cheap fixes would remove that entirely — register
bashas a real catalogue alias ofshell, or keep the backend name out of the description, e.g.Run a shell command (this tool is named "shell"; on Windows it executes via bash).weierliteyangming-hue commentedon Oct 23, 2026
Still present on v2.0.32, and it is generated per-request — here is where
Follow-up with the details, now that the
event.toolredirect is confirmed working.1. Reproduced today on v2.0.32. Windows 11,
"shell": "C:\\Program Files\\Git\\bin\\bash.exe",opencode --version→2.0.32(desktop harness). Theshelltool's description still reads, verbatim:Commands run on Windows using bash.2. The source, so nobody has to hunt for it.
packages/core/src/tool/plugin/shell.tsondev:and it is assigned per registration, same file:
So
${shell}is the configured backend shell's name, interpolated straight into the text a model reads at tool-selection time. Two consequences:bash. Whatever shell is configured gets advertised as if it were a tool name:zsh,pwsh,fishbecome equally plausible emissions.bashis merely the likeliest, because it also matches the dominant model prior.3. This is the text/schema mismatch #56193 hit from the outside. That thread closed as not-planned against 9Router (it renamed
shell→shell_clientbecause the description differed from its saved copy), and the reasoning there restates the mechanism: "shellis the only shell tool it registers, and the description changes with the platform". The rename was 9Router's, but the trigger was this sentence advertising a name that is not in the catalogue.4. The cost is not always one call. When the model recovers it is one wasted call plus a user-visible error banner mid-task. #57584 tracks a case where the emission persists over many turns — different cause per that thread (model/history-driven, not descriptive), same failure mode, unbounded cost once the pattern is in the history. The
execute.beforeplugin removes the symptom for users who know the bug exists and can write TypeScript; a default install has nothing.5. Minimal fix that keeps the information. The catalogue already carries the tool's name, so the sentence need not introduce a second candidate noun — either of:
The alternative — registering
bashas a real catalogue alias ofshell— fixes the recovery path rather than the emission, which is why I still prefer the wording change. Happy to send a one-line PR for either wording; it changes no behaviour, only the string. Also happy to test a build from a branch in this harness if that helps.Doc4581 commentedon Oct 27, 2026
Independent reproduction on Linux 2.1.4, with no Windows
bash.exeand noshelloverride. Same symptom, but a different source of the prior, so it is not only the description text.Environment: opencode 2.1.4, Ubuntu 24.04 x86_64, zsh, tmux. Plugins: superpowers,
@tarquinen/[email protected]. Noshellsetting inopencode.jsonc.Evidence from
~/.local/share/opencode/opencode.dbon this machine:23,409 completed
bashcalls from the V1 era vs 2,974shellunder V2. The models here ran under V1 for months, sobashis a strong prior even when the tool description never says "bash".Since upgrading to V2 (2026-10-26): 789 successful
shellcalls and 3 rejectedbashcalls.All three were in one 4.5-hour session (agent
architect, modeldeepseek-v4-pro), and each failing assistant message also carried a valid parallel call (read), e.g.{"name": "bash", "input": {"command": "cd …/ui && cat vitest.config.*"}} {"name": "read", "input": {"path": "…"}}The same session had successful
shellcalls before and after, so this is intermittent model drift, not a permission rule or a restricted toolset.The alias (or the
execute.beforeredirect — thanks for that) is needed for V1-history models too, not just the Windows description case. A dispatch-level alias map would also cover the other renamed V1 names if a model emits them (task→subagent).