denoland/deno · error · TypeError
Cannot collect output: 'stdout' is locked
Error message
Cannot collect output: 'stdout' is locked
What it means
ChildProcess.output() internally calls collectOutput() on child.stdout, which needs to acquire a reader on the stream. If you already called getReader(), pipeTo(), or pipedThrough() on child.stdout, the stream is locked and output() throws a TypeError before doing anything. You must choose either manual streaming reads or output(), not both.
Source
Thrown at ext/process/40_process.js:535
};
signal?.[abortSignal.add](onAbort);
const waitPromise = op_spawn_wait(this.#rid);
this.#waitPromise = waitPromise;
this.#status = PromisePrototypeThen(waitPromise, (res) => {
signal?.[abortSignal.remove](onAbort);
this.#waitComplete = true;
return res;
});
}
#status;
get status() {
return this.#status;
}
async output() {
if (this.#stdout?.locked) {
throw new TypeError(
"Cannot collect output: 'stdout' is locked",
);
}
if (this.#stderr?.locked) {
throw new TypeError(
"Cannot collect output: 'stderr' is locked",
);
}
const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([
this.#status,
collectOutput(this.#stdout),
collectOutput(this.#stderr),
]);
return {
success: status.success,
code: status.code,View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Pick one strategy: either await child.output() alone, or read child.stdout yourself and collect chunks manually
- If a reader was acquired by mistake, call reader.cancel() / releaseLock() on the existing reader before calling output() — but note partial data already consumed is lost
- Restructure: pipe stdout yourself if you need incremental processing, then assemble the final Uint8Array from the chunks you collected
Example fix
// before const child = cmd.spawn(); const reader = child.stdout.getReader(); const out = await child.output(); // TypeError: 'stdout' is locked // after const child = cmd.spawn(); const out = await child.output(); const text = new TextDecoder().decode(out.stdout);
Defensive patterns
Strategy: type-guard
Validate before calling
function canCollectOutput(child: Deno.ChildProcess): boolean {
return !(child.stdout?.locked ?? false) && !(child.stderr?.locked ?? false);
}
// call before: if (canCollectOutput(child)) await child.output(); Type guard
const outputSafe = (child: Deno.ChildProcess): boolean => child.stdout?.locked !== true && child.stderr?.locked !== true;
Try / catch
try { out = await child.output(); } catch (err) { if (err instanceof TypeError && err.message.includes("is locked")) { /* fall back to manual reader loop */ } else throw err; } Prevention
- Pick one consumption model per child: manual streaming or output(), never both
- Document at the call site that a reader has been attached, so later output() calls are obviously wrong
- If you must recover, reader.releaseLock() on the outstanding reader before retrying output()
When it happens
Trigger: Calling const reader = child.stdout.getReader() (or child.stdout.pipeTo(w)) and then awaiting child.output(); also calling child.output() twice after the first call locked/consumed the stream via a prior manual read.
Common situations: Starting to stream logs incrementally, then deciding to also await the combined output; helper functions that attach readers for progress display while the main path calls output(); calling output() as a fallback after a partial pipe fails.
Related errors
- Cannot collect output: 'stderr' is locked
- Cannot get 'stdout': 'stdout' is not piped
- Piped stdin is not supported for this function, use 'Deno.Co
- ERR_STREAM_NULL_VALUES
- Cannot get 'stderr': 'stderr' is not piped
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/e06136a16a9139ce.
Report an issue: GitHub.