A background spawn failure currently resembles a signal-less kill, while a sandbox-runner failure resembles a completed wrapper exit. The generic task adapter cannot mark both as failed without a distinct infrastructure-failure fact on BashProcess. Mapping the existing states differently would also risk treating ordinary nonzero command exits as infrastructure failures. Keep the current truthful model-visible diagnostics and record a scoped TODO at processOutcome. The eventual fix must widen the BashProcess seam first, then map spawn and runner failures to TaskOutcome failed while preserving completed for real command exits.
@deepseek-ai/dsh-tool-bash
The model-facing bash tool registered over the ctx.bash executor seam. Foreground execution stays behind that seam; a background process handle is registered with the generic ctx.tasks runtime and controlled through task_output, task_list, and task_kill from @deepseek-ai/dsh-tool-tasks.
Requires a loaded executor implementation (e.g. @deepseek-ai/dsh-bash-local); the plugin stays pending until ctx.bash exists (inject: ['tools', 'bash', 'systemPrompt']).
The plugin also contributes the tool:bash prompt section (order 105): check the [exit code: N] marker on every result and investigate failures before moving on.
Tools
bash
| Arg | Type | Notes |
|---|---|---|
command |
string (required) | Run via bash -c. No state persists between calls — use workdir, not cd. |
description |
string (required) | One-line, active-voice summary of the command (5-10 words), for UI/log display only — no effect on execution. |
timeoutMs |
number | Timeout override in milliseconds. The executor applies its configured default and cap. |
workdir |
string | Working directory for this call. Defaults to the calling agent's session cwd (session.header.cwd) so each session runs in its own workspace; a relative workdir is resolved against that session cwd. |
run_in_background |
boolean | Return a task id immediately; no timeout applies. |
sandbox_permissions |
string enum | ADVERTISED ONLY when the mounted executor sandboxes (ctx.bash.sandboxMode reports a confining default): the wider mode a denied command needs, from the closed target vocabulary workspace-write/danger-full-access (never cut down to the executor's default — the effective mode is per-session; strict widening is checked at execution against it, and a non-widening request fails without prompting anyone). |
justification |
string | Required together with sandbox_permissions (each without the other is a validation error): one sentence for the user explaining why this exact command needs the wider access. |
command, workdir, and timeoutMs are resolved against the executor's config defaults via ctx.bash.resolve() before execution, so the executor seam (BashExecSpec) receives explicit workdir/timeoutMs values. The workdir default is applied in the tool layer (from the calling agent's session.header.cwd) BEFORE resolve() — the per-session cwd must come from exec.agent, since N sessions share one executor; only when no session cwd is available does the executor fall back to its own config / process.cwd().
Result text contains stdout, an optional [stderr] section, then applicable sandbox-denial, timeout, signal, exit-code, and truncation markers. Timeout is reported independently of final exit status; nonzero exit remains a model-interpreted result rather than isError. Truncation links a safe complete spill file or reports it unavailable. Only infrastructure failures such as spawn errors and aborts produce isError.
When run_in_background is true, this plugin preflights ctx.tasks.start() before spawning, registers the calling agent as owner, and adapts the returned BashProcess handle into generic cancel/done/incremental-output hooks. The task runtime owns ids, cross-session isolation, completion notices, waiting, and disposal cleanup; this plugin only maps bash exit/sandbox facts into task output and outcome detail. enableRunInBackground: false removes the parameter and rejects a forced background call at execution time.
UI presentation
The tool owns its presentCall/presentResult render intent. A foreground call is a terminal card carrying command, description, cwd, raw output, and parsed exit status. A background start is a generic execute card because it returns only a task id; the generic task_* tools own their own cards. These presenters are pure and replay-safe.
The tool builds its request from named args only
The BashExecRequest seam carries optional stdin and env, used by trusted in-process plugins. This tool does not expose or forward them: it builds requests from named command/workdir/timeout/signal/sandbox fields only. This is not a trust boundary; the local executor's ambient credential scrub is the security control.
Permissions and escalation
Commands run with the executor's full authority unless a sandboxing executor (dsh-bash-sandbox) confines them — the deny-only sandbox reports denials as result facts, rendered here as the denial marker; per-call allow/deny/ask policy is the tools/pre-execute waterfall (see docs/architecture.md).
Escalating bash calls resolve ctx.approval before execution. allowed-once applies the requested mode only to that call; rejection, cancellation, unavailability, or missing approval context executes nothing and returns a distinct error. On a real denial, the model may retry the same command once in the same turn with the narrowest sufficient mode and justification; the approval prompt itself is the consent step. Escalation is never speculative, and a disabled or rejected approval is final. The sandbox RFC owns the rationale.
Per-session mode switching
For sandboxing executors, each call resolves mode as one-shot escalation, then session override, then executor default. Non-sandboxing and agent-less calls carry no session override. Neither the prompt nor a switch notice announces the standing mode; denial results report the effective mode when the boundary matters. See the dsh-bash fold and sandbox switching contract.
Model Experience
System prompt
What the model sees: Every request in this plugin's registration scope contains the bash guidance below. A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section.
Token effect: Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches.
Bash guidance
Check the [exit code: N] marker on every bash result; investigate failures before moving on.
Tool schemas
What the model sees: The model sees the generated bash schema. run_in_background appears only when this producer enables it; sandbox_permissions and justification appear only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definition for that agent.
Token effect: Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields and its conditional description paragraph.
Foreground result
What the model sees: The renderer emits the data-dependent stdout tail, then optional [stderr] and the stderr tail. With no output it emits exactly (no output). Conditional lines are exactly [output truncated; full output: <path-or-(unavailable)>], [sandbox: file access denied under <mode> mode], [timed out after <timeoutMs>ms], [killed by signal: <signal>], and [exit code: <exitCode>]; the sandbox escalation and runner-failure lines are quoted in dsh-bash-sandbox.
Token effect: Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction.
Background task context and results
What the model sees: Start returns exactly started background task <taskId>. This producer supplies incremental process output, optional [some output was dropped from memory; full output: <paths-or-(unavailable)>], sandbox facts, and terminal detail such as exit code: <exitCode> or signal: <signal> to the generic task runtime. dsh-tool-tasks owns the visible status line, completion notice, listing, and cancellation response.
Token effect: The start acknowledgement is small and retained; collected output is data-dependent and bounded by the executor's stream buffers. Consuming reads do not repeat prior output.
Tool errors
What the model sees: Validation and policy failures are normalized as Error: <message>. This package's stable messages are invalid command: expected a non-empty string, invalid description: expected a non-empty string, invalid timeoutMs: expected a positive number, got <value>, invalid escalation: sandbox_permissions requires a justification, invalid escalation: justification is only valid together with sandbox_permissions, invalid justification: expected a non-empty sentence, background execution is disabled for this bash tool, background tasks unavailable: load @deepseek-ai/dsh-tasks and @deepseek-ai/dsh-tool-tasks, sandbox_permissions is not available in this composition (no sandboxing executor to escalate), sandbox escalation to "<mode>" is not strictly wider than this call's current "<mode>" mode, the approval-availability/rejection/cancellation variants, and command aborted.
Token effect: Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run.
Known Limitations and Deferred Work
- Replay exit pills parse from result text — output whose final line happens to be exactly
[exit code: N]/[killed by signal: …]shows a wrong pill on session replay; a display-only known residual. - The
bashtool opts out oftimeout-policybudgets — it keeps the executor-ownedBASH_TIMEOUTpath, per the tool-call timeout-policy RFC. - Background processes have no executor timeout — callers must use
task_kill, or rely on owner/service disposal, when work no longer matters.