032-stdout-closed-reader
A closed reader is not an error: stdout writes stop panicking the CLI
Exit codes are a stable contract: 0 ok, 1 validation failure, 2 stale, 3 I/O. The CLI could exit 101. `println!` unwraps its write, so a reader that stops early made the process panic: `spec-spine registry list --json | head` printed a Rust backtrace and exited 101, and under `set -o pipefail` that failed the surrounding script. Piping into `head`, `less` or `grep -q` is ordinary use, not an error condition. This spec routes every CLI stdout write through one helper that classifies the write instead of unwrapping it: a closed reader ends the process 0, because the consumer got what it asked for, and a genuine I/O failure reports and exits 3, which is the code the contract already assigns to I/O. Stderr keeps `eprintln!`: diagnostics are small, and a broken stderr has nowhere left to report itself. The fix is not `unsafe` SIGPIPE restoration, which `forbid(unsafe_code)` rules out workspace-wide, and not a panic hook, which would match on a message string the standard library is free to change.
Read the full pinned specification ↗Relationships
depends on
Claimed units
- establishes
{ "kind": "file", "path": "crates/spec-spine-cli/src/out.rs" } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/main.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_attest.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_compile.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_couple.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_index.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_lint.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/cmd_registry.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/src/verify_attestation.rs" } } - extends
{ "nature": "additive", "spec": "001-compile-registry", "unit": { "kind": "file", "path": "crates/spec-spine-cli/tests/cli.rs" } }
Source headings
- 032: A closed reader is not an error
- 1. Purpose
- 2. Territory
- 3. Behavior
- 3.1 Two entry points, one classifier, three outcomes
- 3.2 Why not the two shorter fixes
- 3.3 Stderr is deliberately unchanged
- 3.4 Determinism and output are unaffected
- 3.5 Tests (minimum)
- 4. Out of scope
Frozen anchors
No frozen anchors declared.