# Conflicts: # docs/architecture.md # docs/capability-seams.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/core-data-structures/bash.md # docs/module-graph.md # docs/tool-catalog.md # examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md # examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/workspace-edit/tool-schemas.golden.json # packages/bash/bash-local/README.md # packages/bash/bash-local/src/index.ts # packages/bash/bash-local/tests/executor.spec.ts # packages/bash/bash/README.md # packages/bash/bash/src/index.ts # packages/bash/bash/src/types.ts # packages/bash/tool-bash/README.md # packages/bash/tool-bash/package.json # packages/bash/tool-bash/src/index.ts # packages/bash/tool-bash/tests/tools.spec.ts # packages/bash/tool-bash/tsconfig.json # packages/cordis/tool-cordis/src/api-catalog.ts # packages/examples/agent-spine-demo/README.md # packages/examples/agent-spine-demo/src/index.ts # packages/examples/agent-spine-demo/tests/agent-core.spec.ts # pnpm-lock.yaml
@deepseek-ai/dsh-bash-local
Local-subprocess implementation of the @deepseek-ai/dsh-bash executor seam: LocalBashExecutor spawns bash -c <command> per call in its own process group, collects bounded output with full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group.
The package root exports the default and named LocalBashExecutor plugin plus its Config; subprocess plumbing stays internal to the implementation package.
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
graceMs: 3000 # SIGTERM→SIGKILL escalation grace on kills
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/run.tsrecords the two proven stateful designs (Claude Code's cwd-only persistence; Codex's PTY exec sessions) for when real workflows demand them. - Process-group kills with escalation — children are spawned
detached(own process group); kills send SIGTERM to the group, then SIGKILL after thegraceMsgrace (default 3s — OpenCode's escalation; pipelines and subshells die with the parent). ESRCH is tolerated; daemons that re-parent away from the group can still survive — same caveat as the surveyed tools. - Model-friendly env + credential scrub —
process.envminus credential-shaped vars (*KEY*/*SECRET*/*TOKEN*) and all ambientDSH_*names, thenNO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat(Codex's hardcoded set) so pagers and ANSI color don't garble results. A spec's ordinaryenvis merged after the scrub but rejectsDSH_*; manageddshEnvrejects ordinary names and merges last, preventing stale nested-harness identity. Supplied stdin is written and closed; otherwise fd 0 is/dev/null. See the stdin/env RFC and managed environment RFC. - Background processes —
start()returns a liveBashProcesshandle immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), the handle'sreadOutput()is incremental with whole-stream byte offsets, and disposal kills every running process and awaits its exit. 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.
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, detached process groups, group kills, and SIGTERM→SIGKILL escalation are hardcoded; Windows is unsupported. - The credential scrub is a name heuristic —
*KEY*/*SECRET*/*TOKEN*only; differently-named secrets (e.g.*PASSWORD*) pass through, and a whitelist for over-scrubbed vars is noted future work. - Spill files are never deleted — full-output recovery files (and the private per-process spill dir) accumulate under the OS tmpdir until something external cleans them.
The raw process handling lives in src/run.ts; src/index.ts is the service wiring.