Page 8: Standard Streams, File Descriptors, and Exit Codes in Program Execution
If a CLI tool prints progress warnings to `stdout` instead of `stderr`, piping its JSON output into another tool corrupts the downstream parser. Similarly, returning exit code `0` when a build script fails causes broken code to deploy to production.
One of the most enduring engineering designs in computing history is the UNIX philosophy of universal text streams. When the operating system launches a command-line process, it automatically attaches three communication channels identified by integer File Descriptors: Standard Input (`stdin`, descriptor `0`), Standard Output (`stdout`, descriptor `1`), and Standard Error (`stderr`, descriptor `2`).
Because programs read from `stdin` and write structured output to `stdout` without needing to know whether they are connected to a human keyboard, a file on disk, or another program in a pipeline, small focused tools can be composed together effortlessly. Furthermore, when a process finishes, it returns an integer exit code (`0` to `255`) to its parent process—a convention that powers every modern CI/CD build pipeline.
8.1 The Three Standard Streams: stdin (0), stdout (1), stderr (2)
Separating `stdout` (file descriptor 1) from `stderr` (file descriptor 2) solves a critical composability problem. Suppose a program converts a CSV file into JSON and writes the JSON result to `stdout` so it can be piped into `jq` or redirected to `output.json`.
If a warning or error occurs during conversion, writing that diagnostic message to `stderr` displays it on the developer’s terminal screen immediately without polluting `output.json`. In JavaScript/Node.js, `console.log()` writes to `stdout`, while `console.error()` and `console.warn()` write to `stderr`.
8.2 Process Exit Codes and CI/CD Gates
Every process terminates with an integer exit status. By universal convention, exit code `0` means "completed with zero errors." Any non-zero exit code (`1` to `255`) signals an error condition.
Shell operators like `npm run lint && npm run build` inspect the exit code of the first command; if `npm run lint` exits with code `1`, the shell immediately aborts the chain and never runs `npm run build`.