{"record":{"id":"b0d57647a710f6f1","repo":"can1357/oh-my-pi","slug":"process-exited-with-code-exitcode-stderr","errorCode":null,"errorMessage":"Process exited with code ${exitCode}:\n${stderr}","messagePattern":"Process exited with code (.+?):\n(.+?)","errorType":"exception","errorClass":"NonZeroExitError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ptree.ts","lineNumber":321,"sourceCode":"\t}\n\tget stdin(): Bun.SpawnOptions.WritableToIO<In> {\n\t\treturn this.proc.stdin;\n\t}\n\n\t/** Raw stdout stream. Must be consumed to prevent pipe deadlock. */\n\tget stdout() {\n\t\treturn this.proc.stdout;\n\t}\n\n\t/** Optional stderr stream (only when requested in spawn options). */\n\tget stderr() {\n\t\treturn this.#stderrStream;\n\t}\n\n\tget exitedCleanly(): Promise<number> {\n\t\tif (this.#nothrow) return this.#exited;\n\t\treturn this.#exited.then(code => {\n\t\t\tif (code !== 0) throw new NonZeroExitError(code, this.#stderrTail);\n\t\t\treturn code;\n\t\t});\n\t}\n\n\t/** Returns the truncated stderr tail (last 32KB). */\n\tpeekStderr() {\n\t\treturn this.#stderrTail;\n\t}\n\n\tnothrow(): this {\n\t\tthis.#nothrow = true;\n\t\treturn this;\n\t}\n\n\tkill(reason?: Exception, gracefulMs?: number) {\n\t\tif (reason && !this.#exitReasonPending) {\n\t\t\tthis.#exitReasonPending = reason;\n\t\t\t// The normalized exit promise may already have resolved from a dead","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ptree.ts#L303-L339","documentation":"NonZeroExitError is thrown by the `exitedCleanly` getter when a managed subprocess terminates with a non-zero exit code and the process was not spawned with nothrow semantics. The message includes the exit code and a truncated stderr tail (last 32KB) so the caller can see why the child failed.","triggerScenarios":"Awaiting `process.exitedCleanly` on any PTree-managed subprocess that exits with code != 0 — e.g. a build step returning 1, a shell command hitting a missing binary (127), or a killed process (signal-derived codes).","commonSituations":"Running git/jj or package-manager commands via execText/captureText helpers where the command fails; scripts assuming success; flaky network commands.","solutions":["Read the stderr tail in the error message to find the child's actual failure.","Fix the underlying command or its arguments/inputs.","If non-zero exit is acceptable, construct the process with nothrow (or use `.exited` directly) instead of `exitedCleanly`.","Wrap in try/catch and handle NonZeroExitError when failure is expected."],"exampleFix":"// before\nawait proc.exitedCleanly;\n// after\ntry {\n  await proc.exitedCleanly;\n} catch (err) {\n  console.error(err.message); // includes code + stderr tail\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isNonZeroExitError(err: unknown): err is { code: number; stderrTail?: string } {\n  return err instanceof Error && /Process exited with code \\d+/.test(err.message);\n}","tryCatchPattern":"try {\n  await proc.exitedCleanly;\n} catch (err) {\n  if (/Process exited with code/.test(err.message)) {\n    logger.error('child failed', { stderr: err.message });\n  } else throw err;\n}","preventionTips":["Check exit codes of every spawned command instead of assuming success.","Use nothrow construction when non-zero exit is an expected branch.","Include the stderr tail in logs when reporting child failures."],"tags":["subprocess","exit-code","stderr"],"backgroundTag":"non-zero-exit-code","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}