Skip to content

fix(policy): name the shell tool by its published name - #306

Merged
ViscaOwO merged 2 commits into
T-Auto:mainfrom
sueqet:fix/policy-name-the-published-shell-tool
Oct 21, 2026
Merged

ViscaOwO merged 2 commits into
T-Auto:mainfrom
sueqet:fix/policy-name-the-published-shell-tool

Conversation

@sueqet

@sueqet sueqet commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Closes #298

What was wrong

The tooling policy told the model to call a tool that does not exist.

renderToolingPolicy() builds the model-visible system prompt, and three model-facing strings named the shell bash while the registry only publishes BASH_TOOL = ops_bash:

  • src/policy.ts:43 — the heading # Shell routing: bash → host PowerShell
  • src/policy.ts:91 — Prefer bash for command execution. ... fix bash errors in bash ...
  • and the table row rendered at row('bash', ...), which does resolve through publicToolName()

Issue #298 has the measurement: on DSH Desktop 0.2.1-rc.2, [email protected], Windows x64, workspace-write, a third-party gateway model issued {"name":"bash", ...} 15 times in one session, each returning:

Error: unknown tool "bash"

That session's request/header listed 44 tools including ops_bash and not bash; the desktop profile ships dsh-tool-bash with disabled: true, so the tool genuinely does not exist. The prompt was teaching a name the registry never publishes, and the model only recovered by retrying with the real name.

The change

src/policy.ts (2 lines changed, 1 added):

  • The heading and the prefer-line are now built from BASH_TOOL instead of the literal bash, so they cannot drift from the published name again. The heading became a template literal for that reason.
  • Added one line that states the fact outright: There is no tool named bash in this deployment. The shell tool is ops_bash; a call to bash returns unknown tool and wastes the step. A model that leans on the Claude Code vocabulary — which is where the observed bash calls came from — now has an explicit rule to read instead of inferring the name from prose.

test/policy.test.mjs:

  • The existing shell-routing test now asserts against BASH_TOOL rather than the literal string.
  • New test the shell is never advertised under a name the registry does not publish scans every model-visible line and fails if any line other than the prohibition names bash. It also asserts that a file-only deployment contains no bash at all.

docs/CHANGELOG.md: ## [Unreleased] entry in both languages.

Deliberately not done: the ops_bash requires description ... contract line keeps its literal ops_bash, because it describes the tool contract rather than routing, and changing it would add schema/prompt bytes for no accuracy gain. No other bash occurrence in src/ is model-visible.

Prompt-byte impact: the heading is the same length (bash → ops_bash is 4 bytes longer), the prefer-line gains 12 bytes, and the new prohibition line adds 123 bytes, once per model step where the shell is published.

Validation

$ node test/run.mjs policy
=== policy.test.mjs
  ok   an empty registry produces no tooling guidance
  ok   file guidance names exactly the available file tools
  ok   each discovered file capability has routing only while it is visible
  ok   the prompt states the real file-tool syntax and result contract (#44)
  ok   partial tool availability never advertises missing tools
  ok   resident-only routing omits unavailable operation hints and includes discovery only when published
  ok   shell routing prefers bash and leaves PowerShell ownership with the host
  ok   the shell is never advertised under a name the registry does not publish
  ok   the removed background tools are never advertised, even if a registry still lists them
  ok   levels are registry facts, never just resolved executables
  ok   deployment guidance is appended and transport vocabulary stays hidden
  ok   explicit deny refuses only configured names even without ops commands
  ok   legacy second section is retired; component section prefix stays stable

dsh-ops policy: all 13 checks passed
dsh-ops test: 1 suite(s) passed in 0s

Red/green, with src/policy.ts reverted and rebuilt (git stash push src/policy.ts && npm run build):

dsh-ops policy: 2 of 13 check(s) failed
--- policy.test.mjs exited 1
dsh-ops test: 1 of 1 suite(s) failed in 0s

Restored, rebuilt:

dsh-ops policy: all 13 checks passed

Wider suites:

$ node test/run.mjs shells
=== shells-session.test.mjs skipped (no provisioned or bundled bash; allowSystemShellFallback is false)
dsh-ops test: 9 suite(s) passed, 1 skipped in 3s

$ npm run check:size
check-file-size: 374 code file(s) checked, all at or under 1000 lines (longest: bench/setup/profile.mjs (924))

The single skip is the pre-existing "no bash payload installed in this checkout" skip; it is unrelated to this change and is a failure under DSH_OPS_REQUIRE_RUNTIME=1, which I could not satisfy locally without installing the pinned payload.

  • DSH_OPS_REQUIRE_RUNTIME=1 npm run verify — not run in full: this checkout has no Linux/macOS worker or bash payload, so the runtime-requiring suites would fail for environment reasons rather than this change. Please let CI be the reference for that gate.
  • Reverting the implementation turns 2 checks red (output above)
  • docs/CHANGELOG.md ## [Unreleased] updated (bilingual)
  • No docs describe the old bash wording
  • No dependencies added; package.json version and optionalDependencies untouched
  • Model-facing description change: prompt-byte impact stated above; no schema change

Environment

  • Windows 11 x64, Node v24.20.0, npm 11.19.0
  • Based on T-Auto/dsh-ops main at dd67dca (Merge pull request docs: DSH 工具注入分析合集 #299)
  • Built with npm ci --ignore-scripts then npm run build

The tooling policy called the shell 'bash' in the routing heading and in
the prefer-for-command-execution line, but the registry publishes
'ops_bash'. The model read a tool name it could not call: T-Auto#298 recorded 15
'unknown tool bash' calls in one session before it switched on retry.

Build both strings from BASH_TOOL so they cannot drift from the published
name again, and state outright that no bash tool exists, so a model that
leans on the Claude Code vocabulary has an explicit rule to read.

A new assertion fails if any model-visible line other than that prohibition
names bash; reverting the source turns it red.
@sueqet
sueqet force-pushed the fix/policy-name-the-published-shell-tool branch from e2d7552 to 7683ba4 Compare October 20, 2026 06:59
@ViscaOwO
ViscaOwO self-requested a review October 21, 2026 13:20
…T-Auto#306)

The routing heading and the "prefer for command execution" line named a tool
the registry does not publish.

The prohibition line added in T-Auto#306 is dropped: the renderer only receives
`published` filtered to the plugin's own names, and under the default
`shellPolicy: advise` the host `bash` stays visible on Linux and macOS, so the
claim would be false there. A denied host shell reports a policy denial, not
`unknown tool`.

The test added in T-Auto#306 is rewritten: it rejects a bare `bash` (case
insensitive) without depending on that sentence.

Co-authored-by: sueqet <[email protected]>
@ViscaOwO

ViscaOwO commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

LGTM
Thanks for your contribution :)
有点小问题,修复已直接推到你分支(df93c1c)非常有价值的PR,赞

@ViscaOwO ViscaOwO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer patch: the last commit is mine (df93c1c); @sueqet's 7683ba4 is preserved and his design intent is unchanged.

@ViscaOwO
ViscaOwO merged commit b963fb2 into T-Auto:main Oct 21, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 提示词把 shell 工具写成 "bash",模型会调用不存在的 bash(unknown tool "bash")

2 participants