Skip to content

fix(io): add quiet exit and reject out-of-range status codes - #1492

Merged
Lorenzobattistela merged 3 commits into
bendlang:mainfrom
oxura:fix/1465-quiet-io-exit-20261011
Oct 11, 2026
Merged

Lorenzobattistela merged 3 commits into
bendlang:mainfrom
oxura:fix/1465-quiet-io-exit-20261011

Conversation

@oxura

@oxura oxura commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1465 using the narrower design requested in the maintainer review of #1474. This replaces that closed redesign; IO(U32) main's result is still ignored.

Change

  • Add pure checked IO.exit(code: U32) -> IO(Unit) and a distinct Exit{code} IO opcode. A valid exit writes nothing, including no empty stderr line. No user-defined foreign-effect workaround.
  • Validate 0..255 in the shared interpreter/JS runtime and native host loop. Invalid IO.exit and IO.die codes fail with status 1 instead of OS modulo-256 wrapping. IO.die still writes its caller-supplied message and newline.
  • Decode a packed native opcode directly from its immediate payload, not as a boxed heap address. The one-field U32 Exit constructor is emitted as term_pak.
  • Treat Exit as a genuine IO.OP constructor in user wildcard guards and migrate the repository's exhaustive IO.OP consumers. Foreign requests still cannot enter those pure matches.
  • Add consumer-visible termination/wildcard and boundary regressions. The boundary probe observes actual subprocess status, before/after reachability, byte-exact valid quiet stderr and IO.die's empty-message newline; it does not pin the range diagnostic sentence.
  • No GUIDE or CHANGELOG edit; no main/argv policy change, checker, Lean kernel, protected gate, allow-list or cap change.

Actual evidence

Linux x64, real generated programs, not source assertions:

  • 6 terminal scenarios × 4 actual lanes (source interpreter, standalone Bun JS, standalone Node JS, native C): valid quiet 0/255 produce zero stderr bytes; 256/UINT32_MAX fail 1; empty-message IO.die keeps its one newline and code 7; IO.die(256, "reported") retains its caller message and fails 1. All print only before, never the continuation's after.
  • Returned U32 main: prints kept, exits 0 in all four lanes, not its returned 37.
  • Quiet opcode/wildcard regression: prints 91, exits 23, zero stderr in interpreter, Bun, Node and native C.
  • Both existing alien-request tests retain their output, fail-stop status 1 and never print SECRET in all four lanes. The UTF-8 halt law retains è❁ and status 7 in all four lanes.
  • Genuine installed-layout compiled Bend CLI, empty PATH: quiet termination, the six-case boundary fixture and unchanged typed-main behavior all match. The final boundary fixture also prints 0 in source, standalone Bun and Node execution.
  • Pure structural exit theorem: the genuine unmodified BendTT kernel prints ALL PROOFS CHECK, exit 0. This proves formal admissibility, not a proof of OS exit behavior.
  • Actual namespaced --checkup exercised all five opcode consumers with empty PATH, including their expected nonzero termination cases; no claim that its aggregate process returned 0.
  • Repository shape/cap gate: 54/54.

Baseline wrapping is the issue/maintainer's reported observation; it was not redundantly rerun to confirm it. Mini-cluster/GPU gates were not run.

@oxura

oxura commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Applied the maintainer documentation rule from #1491: my CHANGELOG entry is removed in 75aa492. There is no GUIDE or CHANGELOG diff; the tested IO.exit/IO.die implementation and regression fixtures are unchanged.

@Lorenzobattistela
Lorenzobattistela merged commit d71f42e into bendlang:main Oct 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

main cannot set an exit status without writing to stderr, and IO.die codes above 255 wrap (256 exits 0)

2 participants