denoland/deno · error · TypeError

Cannot collect output: 'stderr' is locked

Error message

Cannot collect output: 'stderr' is locked

What it means

ChildProcess.output() internally calls collectOutput() on child.stderr, which needs to acquire its own reader. If a prior getReader(), pipeTo(), or pipedThrough() on child.stderr locked the stream, output() throws a TypeError immediately. stderr and stdout are checked independently, and the stdout check runs first.

Source

Thrown at ext/process/40_process.js:540

      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,
      signal: status.signal,
      get stdout() {
        if (stdout == null) {
          throw new TypeError("Cannot get 'stdout': 'stdout' is not piped");
        }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Choose one consumption model: full capture via output(), or manual reader on .stderr (and .stdout) without calling output()
  2. Release the mistaken lock with reader.releaseLock() or cancel() before calling output()
  3. If you need live stderr plus the final combined output, read both streams manually and build the result yourself

Example fix

// before
const child = cmd.spawn();
child.stderr.pipeTo(logSink);
const out = await child.output(); // TypeError: 'stderr' is locked

// after
const child = cmd.spawn();
const out = await child.output();
console.errorText = new TextDecoder().decode(out.stderr);
Defensive patterns

Strategy: type-guard

Validate before calling

function canCollectOutput(child: Deno.ChildProcess): boolean {
  return !(child.stdout?.locked ?? false) && !(child.stderr?.locked ?? false);
}

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("'stderr' is locked")) { /* consume streams manually instead */ } else throw err; }

Prevention

When it happens

Trigger: Acquiring a reader on child.stderr (e.g. to stream error output live) and subsequently awaiting child.output(); attaching a pipe to stderr for logging, then calling output() for the final result.

Common situations: Streaming stderr to a console while also wanting the captured result; error-handling wrappers that read .stderr before delegating to output(); mixing manual reads for one stream with output() for both.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/22aaee22e004b85b. Report an issue: GitHub.