@deepseek-ai/dsh-bash-local
English | 中文
Local implementation of the @deepseek-ai/dsh-bash executor seam over the @deepseek-ai/dsh-subprocess service: LocalBashExecutor spawns bash -c <command> per call as a managed process group through ctx.subprocess, and owns everything bash-shaped — command defaulting and caps, timeout/cancel classification, the model-friendly terminal environment, and the model-facing stdout/stderr merge for background reads. Group mechanics (bounded spill-backed output, credential scrub, kill escalation, disposal) are the subprocess service's.
The package root exports the default and named LocalBashExecutor plugin plus its Config.
Config
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: /path/to/workspace # default: process.cwd()
timeoutMs: 120000 # default foreground timeout
maxTimeoutMs: 600000 # cap for per-call overrides
maxOutputBytes: 64000 # per-stream in-memory cap; overflow spills to disk
maxSpillBytes: 67108864 # per-stream full-output spill cap
graceMs: 3000 # kill escalation and post-exit pipe-drain grace
Behavior (and where it came from)
Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; the notable choices:
- Spawn per call, no shell state — every call is a fresh non-login
bash -c(deterministic; no rc files). All four surveyed tools spawn per call.XXX(stateful-shell)insrc/index.tsrecords the two proven stateful designs (Claude Code's cwd-only persistence; Codex's PTY exec sessions) for when real workflows demand them. - Configured budgets over managed groups —
resolve()fillsworkdir/timeoutMs/stdoutMaxBytesfrom config, and every spawn hands the service explicit byte caps, spill cap, andgraceMs(default 3s — OpenCode's escalation). The grace must be positive, finite, and no greater thanMAX_TIMER_DELAY_MS, so Node can represent it with one timer. Process-group kills, the post-exit pipe-drain grace, tail-keep truncation, and bounded spill files aredsh-subprocess-localmechanics. A foregroundBashExecRequest.stdoutMaxBytescan raise stdout's capture budget for one trusted caller; stderr and background runs still usemaxOutputBytes. - Timeout and cancel classification —
run()fuses its config-clamped timeout with the caller's signal through one deadline; only the executor's own timeout reportstimedOut, an upstream cancel reportsaborted, and a self-signaled command reports neither (timeout-library Agent Note). - Model-friendly terminal env —
NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat(Codex's hardcoded set) so pagers and ANSI color don't garble results, merged as ordinary env under the service's credential scrub andDSH_*channel rules; an explicit caller entry still wins. See the stdin/env Agent Note and managed environment Agent Note. - Background processes —
start()returns a liveBashProcesshandle immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), and the handle'sreadOutput()merges the service's offset-based stdout/stderr reads into one marked-section delta with a consuming cursor. A still-running process belongs to the subprocess service, so it survives executor reloads and dies (killed and joined) with the service's disposal. Everything task-shaped (ids, ownership, polling, notices) lives in the genericctx.tasksruntime, which the tool layer registers the handle with — this executor never sees a session or a registry.
Model Experience
Indirectly, through dsh-tool-bash, which renders this executor's bounded stdout/stderr tails, background-process deltas, spill-file paths, and infrastructure failures.
KV Cache effect
No direct invalidation; the named consumer owns any request-prefix changes.
Known Limitations and Deferred Work
- Unconfined by itself — this executor always runs commands with the harness process's authority; deployments needing confinement compose
dsh-bash-sandbox, while per-call allow/deny/ask policy belongs ontools/pre-execute. - No persistent shell or PTY — every call starts a fresh non-login
bash -c; cwd-only persistence and interactive terminal sessions remain deferred until a real workflow requires them. - POSIX-only — the
bashbinary is hardcoded, and the underlying service's group semantics are POSIX; Windows is unsupported. - A background spawn-failure note is single-delivery — the subprocess service buffers no output for a process that never ran, so the executor injects
spawn failed: …into exactly onereadOutput()delta; a reader that discards that delta cannot recover it.
Scrub-heuristic and spill-retention caveats live with dsh-subprocess-local, which owns those mechanics.