You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
main cannot set an exit status without writing to stderr, and IO.die codes above 255 wrap (256 exits 0) #1465
This should print the line and exit 2, at the point where an IO(Unit) main exits today. Today it compiles, prints the line and exits 0, natively, in compiled JS and under bend status.bend: the result is silently dropped.
An exit status outside 0..255, whether from IO.die or from main, should fail with a message and a non-zero status instead of wrapping. Today the OS wraps it modulo 256, so a failure can report success:
The same happens with bun big.js and bend big.bend.
Why
Command-line programs signal errors through the exit status: 2 for a usage error, by convention, after a formatted message of several lines. In Bend today, the only way to exit non-zero is IO.die(code, msg), and it always writes msg plus a newline to stderr. #1101 confirms that newline is intended. So a program must either squeeze its whole message into msg, or print the message itself and then accept a stray blank line from IO.die(code, ""). It can never exit non-zero silently, for example after a child process has already reported the error.
The only workaround is a user-written foreign effect that calls exit(). A foreign def makes bend PROOF.bend fail for every law in the import graph (#1463).
Prior art: C int main, Rust fn main() -> ExitCode.
Thanks for the report. We closed #1474 because we don't want main's return value to become the exit status, since that changes the meaning of every existing IO(U32) main.
The direction we'd take instead:
Add IO.exit(code: U32) -> IO(A), which exits with code and writes nothing to stderr. That covers the CLI case: print your own multi-line usage error, then IO.exit(2). You no longer need a foreign exit(), so bend PROOF.bend fails over foreign defs that no law depends on #1463 stops getting in the way too.
Have IO.die and IO.exit check that the code is in 0..255. Today an out-of-range code wraps mod 256 (IO.die(256, "x") exits 0). It should fail with a nonzero status instead.
Activity
Lorenzobattistela commentedon Oct 11, 2026
Thanks for the report. We closed #1474 because we don't want main's return value to become the exit status, since that changes the meaning of every existing
IO(U32)main.The direction we'd take instead:
IO.exit(code: U32) -> IO(A), which exits withcodeand writes nothing to stderr. That covers the CLI case: print your own multi-line usage error, thenIO.exit(2). You no longer need a foreignexit(), sobend PROOF.bendfails over foreign defs that no law depends on #1463 stops getting in the way too.IO.dieandIO.exitcheck that the code is in 0..255. Today an out-of-range code wraps mod 256 (IO.die(256, "x")exits 0). It should fail with a nonzero status instead.mainkeeps working as it does now.