
Security News
GitHub to Block pull_request_target Workflows by Default Starting December 2
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.
@deepseek-ai/dsh-subprocess
Advanced tools
Subprocess seam (ctx.subprocess) for the DeepSeek Harness — managed process groups, bounded spill-backed output, and escalated kills behind one abstract service
English | 中文
ctx.subprocess resolves executables, starts explicitly specified child processes or real terminal sessions, streams or collects bounded output, and terminates the full managed process range. Configure one subprocess implementation for each composition, choosing local or remote execution according to where commands must run. Each request sets argv, working directory, stdio, environment overrides, termination grace, and cancellation, with no shell interpretation or hidden execution defaults. Child environments remove ambient credentials and DSH_* values before applying explicit overrides; callers own deadlines, teardown policy, and model-facing rendering, while collected output remains readable after exit.
Mount a subprocess provider in any composition that must run child processes, and call ctx.subprocess from the capability that owns the command. The common path is explicit: resolve the executable, spawn with a fully specified request, read the output you asked for, and terminate the managed range when the work is done.
One provider registers ctx.subprocess per composition; load it beside the consumers that spawn through it — the bash executors, the LSP host, the PTY shell backend, or an out-of-process subagent backend. Loading a second provider fails loudly (one service per context, Cordis standard).
- name: '@deepseek-ai/dsh-subprocess-local'
- name: '@deepseek-ai/dsh-bash-local'
The request is fully explicit: the program and arguments, the working directory, one stdio disposition per stream, a termination grace, an optional abort signal, and optional environment overrides. Target and managed-range identities remain provider-private. done resolves with the direct command's exit facts (exitCode and signal) and rejects for spawn or provider failures; collected output stays readable after exit.
const executable = await ctx.subprocess.resolveExecutable('bash')
const handle = ctx.subprocess.spawn({
argv: [executable, '-c', 'echo hello'],
cwd: '/workspace',
stdio: { stdin: 'ignore', stdout: { maxBytes: 64 * 1024 }, stderr: 'inherit' },
graceMs: 5000,
})
const { exitCode, signal } = await handle.done
const output = handle.collected.stdout?.readFrom(0)
'pipe' hands you the raw stream for your own protocol framing — JSON-RPC for the LSP host, ndjson for the ACP backend.'inherit' lets the child write to the parent's own stream, for pass-through diagnostics.spill cap and the complete stream is also recoverable from a spill file.Reads are offset-based and non-consuming: a background reader and a final batch read can share one stream without stealing each other's bytes.
Set stdio.control: 'pipe' to receive a separate raw Duplex in handle.control. The Node child opens fd 7 with openInheritedControlChannel() from @deepseek-ai/dsh-subprocess/control; this helper consumes the provider-owned DSH_SUBPROCESS_CONTROL=pipe marker. Callers cannot supply that marker through env. Control bytes never enter stdout/stderr collectors. The consumer owns framing, validation, backpressure, and closing its endpoint; provider disposal destroys any endpoint remaining after process teardown. Omitting the request returns control: undefined. This channel is available only for ordinary processes, and carries no authority to bypass tool approval.
Termination and waiting use one provider-managed range. terminate() starts the provider's documented procedure, is idempotent, and becomes a no-op after that range is empty; the request's abort signal starts the same procedure. waitForExit() observes the same range and resolves only after the provider proves it quiescent, so direct command completion does not hide a surviving descendant. It rejects when the selected owner can no longer prove quiescence. Providers document their native owners and weaker fallbacks; callers own deadlines, teardown ladders, and cause classification.
For interactive programs, spawnTerminal allocates a real PTY: write text, read UTF-8 output, inspect and signal the current foreground process group, and await one terminate() that settles every session member the provider can still observe. Readiness, scrollback, and prompt policy stay with the PTY consumer.
A terminal request can opt into shellActivity. inspectActivity() combines supported shell lifecycle evidence with owned-job observations and returns idle, busy or unknown, plus a handle-scoped revision. Unsupported or incomplete observations never imply idle; input invalidates existing prompt evidence. Opted-in terminals keep remaining work owned after the root shell exits instead of using that exit as permission to terminate descendants. Consumers own retention and cleanup deadlines.
Children never inherit the harness's ambient secrets: credential-shaped names and ambient DSH_* facts are scrubbed, and the caller's explicit env merges after that scrub. A deliberately forwarded credential or a current DSH_* deployment fact still reaches the child; an explicit undefined tombstone removes an ordinary ambient entry.
An executable that cannot be resolved fails loudly with a stable error. A spawn that never starts rejects done; there is no buffered output for a process that never ran. waitForExit() also rejects when the provider cannot prove its selected range is empty, and a provider fallback may not own descendants that escape its process group or observed session. When a transport owns its own spawn (the SDK client, MCP), route around the service and import scrubbedParentEnv directly so environment policy stays single-sourced.
This section explains the design decisions behind the seam and points at the code that realizes them; the observable behavior is covered in Use this package.
The seam is built on one separation: the service owns process coordinates and lifetime; consumers own what a process means and every default that shapes one. That is why the spawn request is fully explicit — no hidden subprocess-service default — and why SubprocessOutcome carries exit facts only: callers own deadlines, teardown ladders, and cause classification. The dsh-shell request/spec split is the owning template.
| File | Role |
|---|---|
src/index.ts | Plugin entry: abstract SubprocessRuntime, ctx.subprocess registration, the shared scrubbedParentEnv scrub |
src/types.ts | Vocabulary: spawn spec, stdio modes, handles, readers, outcomes, DSH_* namespace |
A spawn returns a live handle immediately without exposing target identity. done independently reports the direct command outcome or failure, while waitForExit() reports managed-range quiescence. The request's abort signal drives the same termination procedure as terminate(). Collected readers are cursor-free: offsets are whole-stream byte coordinates the caller owns, so independent readers cannot consume one another's output, and a read whose offset slid out of the in-memory tail is lossy and points at the spill file when one exists. spawnTerminal is one deep primitive because ordinary pipes cannot allocate a controlling terminal or clean terminal-session members.
One implementation registers per context; loading a second throws (Cordis standard). Disposal of the service terminates every still-running managed process and awaits its exit, so process lifetime survives consumer reloads. argv is never shell-interpreted; a consumer that wants a shell passes ['bash', '-c', command] itself. Terminal allocation cancellation (the spec signal) is separate from the published handle's lifetime.
Read these pages when the package-level contract is not enough. They move from the exhaustive type reference to the providers and the decision evidence behind the seam.
DSH_* environment in full.Terminal consumers use terminalEnvironment() to read the provider platform and preferred shell, and resolveExecutable() to verify candidates. A completed lookup miss throws SubprocessExecutableNotFoundError; transport failures remain distinct. spawnTerminal requires terminalType and initial dimensions, and its handle supports resize(cols, rows) without reallocating the process.
Indirectly, through consumer seams such as the bash executor family, which own all model-facing rendering of process output and lifecycle.
No direct invalidation; the named consumers own any request-prefix changes.
These limits define when the seam is a poor fit or leaves work to its consumers. They are current package constraints, not a comparison or a backlog.
scrubbedParentEnv so environment policy stays single-sourced.This Dev Note is working context for maintainers: open design questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
Future: non-shell runners. The seam was split so a direct-argv executor or worker supervisor could consume it without reaching into bash internals; none is shipped yet, and the terminal primitive keeps readiness policy in its consumer.
FAQs
Subprocess seam (ctx.subprocess) for the DeepSeek Harness — managed process groups, bounded spill-backed output, and escalated kills behind one abstract service
The npm package @deepseek-ai/dsh-subprocess receives a total of 629,759 weekly downloads. As such, @deepseek-ai/dsh-subprocess popularity was classified as popular.
We found that @deepseek-ai/dsh-subprocess demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Security News
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.

Security News
/Application Security
A practical guide to securing GitHub Actions against supply chain attacks, covering malicious pull requests, credential theft, dependency risks, and release pipelines.

Security News
Composer 2.11 adds dependency cooldowns, joining package managers that delay new releases to reduce supply chain risks while allowing urgent security updates.