{"record":{"id":"22aaee22e004b85b","repo":"denoland/deno","slug":"cannot-collect-output-stderr-is-locked","errorCode":null,"errorMessage":"Cannot collect output: 'stderr' is locked","messagePattern":"Cannot collect output: 'stderr' is locked","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/process/40_process.js","lineNumber":540,"sourceCode":"      signal?.[abortSignal.remove](onAbort);\n      this.#waitComplete = true;\n      return res;\n    });\n  }\n\n  #status;\n  get status() {\n    return this.#status;\n  }\n\n  async output() {\n    if (this.#stdout?.locked) {\n      throw new TypeError(\n        \"Cannot collect output: 'stdout' is locked\",\n      );\n    }\n    if (this.#stderr?.locked) {\n      throw new TypeError(\n        \"Cannot collect output: 'stderr' is locked\",\n      );\n    }\n\n    const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([\n      this.#status,\n      collectOutput(this.#stdout),\n      collectOutput(this.#stderr),\n    ]);\n\n    return {\n      success: status.success,\n      code: status.code,\n      signal: status.signal,\n      get stdout() {\n        if (stdout == null) {\n          throw new TypeError(\"Cannot get 'stdout': 'stdout' is not piped\");\n        }","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/process/40_process.js#L522-L558","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Choose one consumption model: full capture via output(), or manual reader on .stderr (and .stdout) without calling output()","Release the mistaken lock with reader.releaseLock() or cancel() before calling output()","If you need live stderr plus the final combined output, read both streams manually and build the result yourself"],"exampleFix":"// before\nconst child = cmd.spawn();\nchild.stderr.pipeTo(logSink);\nconst out = await child.output(); // TypeError: 'stderr' is locked\n\n// after\nconst child = cmd.spawn();\nconst out = await child.output();\nconsole.errorText = new TextDecoder().decode(out.stderr);","handlingStrategy":"type-guard","validationCode":"function canCollectOutput(child: Deno.ChildProcess): boolean {\n  return !(child.stdout?.locked ?? false) && !(child.stderr?.locked ?? false);\n}","typeGuard":"const outputSafe = (child: Deno.ChildProcess): boolean =>\n  child.stdout?.locked !== true && child.stderr?.locked !== true;","tryCatchPattern":"try { out = await child.output(); } catch (err) { if (err instanceof TypeError && err.message.includes(\"'stderr' is locked\")) { /* consume streams manually instead */ } else throw err; }","preventionTips":["Do not attach pipes/readers to stderr unless you intend to finish the capture yourself","Keep the locking rule in mind: any getReader/pipeTo/pipedThrough locks the stream","Structure code so the component that locks the stream also fully drains it"],"tags":["deno","child-process","streams","stderr","locked-stream","output"],"backgroundTag":"readable-stream-locked","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}