Skip to main content

HERMIT_INTERNAL_FAILURE_EXIT

Constant HERMIT_INTERNAL_FAILURE_EXIT 

Source
pub const HERMIT_INTERNAL_FAILURE_EXIT: i32 = 125;
Expand description

Exit status for a failure of HERMIT ITSELF, as distinct from the guest’s.

⚠️ EVERY CLI ERROR USED TO BE Exited(1), WHICH IS THE SINGLE MOST COMMON GUEST FAILURE CODE. A tracer panic and a guest that exited 1 of its own accord were therefore INDISTINGUISHABLE from $? alone — and every harness, gate and script on this project decides pass/fail from exactly that value. The information was not missing, only discarded: reverie’s container reports the child’s real status as a typed ExitStatus, and hermit collapsed it to prose and then to 1.

WHY THIS DOES NOT BREAK THE “hermit’s exit IS the guest’s exit” CONTRACT. That contract describes the case where the guest RAN AND EXITED, and it is untouched: a guest status still flows through raise_or_exit unchanged, and only the Err arm — where there is NO guest exit code, because hermit failed before or instead of producing one — uses this value. The contract did not cover that case; it does now.

WHY 125. It is the established convention for “the wrapper tool itself failed” — GNU env, chroot and timeout all reserve 125 for exactly this, leaving 126/127 for exec-level problems. It is not a value hermit emits anywhere else.

⚠️ THIS REDUCES A COLLISION, IT DOES NOT REMOVE ONE. A guest is free to exit 125 deliberately, and every value in 0..=255 is a legal guest status, so no code can be reserved outright. What it removes is the GUARANTEED collision with the most common failure code. For an unambiguous signal, read the HERMIT_TASK_PANIC marker on stderr or the verification JSON; this makes the cheap check — $? — stop actively lying.

⚠️ IT LIVES IN THE LIBRARY, NOT IN main.rs, AND THAT PLACEMENT IS THE FIX FOR HOW IT WENT STALE. It was a private const inside the binary, so tests/cli.rs — the only thing that asserts on it — could not name it and wrote the number out by hand instead. When hermit#2558 moved the value from 1 to 125, the definition moved and the sixteen copies did not: eight cli tests went red on main and stayed red, each failing an assertion about the exit code rather than about the behaviour it exists to check. Same shape as RECORD_REPLAY_HASHES_IO_BUFFERS above — a decision carried in two places that had to agree and had no way to. Exported here, the tests move with it.

⚠️ DO NOT FLIP EVERY Some(1) YOU FIND. tests/stress_suite.rs asserts Some(1) as THE GUEST’S OWN SIGNAL, not as a stale copy of this constant: there Some(1) => GuestOutcome::Exposed, and status.code() == Some(1) IS the exposes_bug predicate. It is pattern-identical to the tests that DID go stale, and it is the OPPOSITE case – the assertion is CORRECT and must not move with this constant.

⚠️ ITS FAILURE MODE IS SILENT SUCCESS. Changing it to 125 would not turn the stress suite red; it would make exposes_bug NEVER FIRE, so the suite would report no bug exposed forever and pass while blind. A red is recoverable; a green that cannot fail is not.

REVIEWER RULE: a change to tests/stress_suite.rs inside an exit-code head SHOULD BE REFUSED ON SIGHT AND QUESTIONED.