ADR-112: A Cleanup That Did Not Finish Is a Failure of the Program — Exit 70, Said on the Panic Path

Status: Superseded by ADR-297 — its D1-D3 are D7-D9 there Date: September 16, 2026 Target Version: Nikaia 0.0.8 Related: ADR-006 D3 and D5 (parked cleanups, and the drain with a deadline), ADR-038 D5 (cleanup-deadline lives in nikaia-runtime.toml), Part I 6.4 (Cleanup) and 7.2 (the panic hook), Part III 13.3b, Part III Appendix A

1. Context

A buffered file flushes, a transaction rolls back, a TLS connection says goodbye: tearing a resource down is I/O, and Cleanup is where a program writes it. When a task is cancelled nobody can wait for its I/O, so the runtime adopts the pending cleanups and finishes them before the program ends, bounded by cleanup-deadline so that the end cannot hang.

What happens when the deadline expires was half said. The remaining cleanups are cancelled, their synchronous fallback runs, and “the program exits with a warning naming every resource that did not finish cleanly.” Which exit status, and where the warning goes, was not said — and an unsaid exit status is 0.

A cancelled cleanup is an unflushed file or a transaction left open. The thing that started the program is almost never a person: it is systemd, cron, a pipeline, a script under set -e, a container supervisor — and none of them reads a warning. They read the exit status, and 0 tells them the page landed when it did not. report.nika’s own reasoning, the page is worth nothing if it did not land, applies to the program that wrote it.

2. Decisions

D1 — An expired cleanup-deadline ends the program with exit status 70.

70 is EX_SOFTWARE from sysexits.h, the Unix convention for the program could not complete its work correctly, which is what happened. It is fixed, documented, and distinct from a panic’s status (an abort is 134 on Linux) so that a script can tell the two apart. A program whose cleanups all finished exits as it would have anyway.

D2 — The message goes the panic path: stderr, and the panic hook.

The message names every resource that did not finish, and it is delivered as a panic’s message is (Part I 7.2): to standard error, and through the program’s panic hook, so that whatever collects crash reports collects this. It is never written to standard output, which belongs to the program’s output and may be the very file somebody is waiting for.

D3 — There is no setting that makes it 0 again.

cleanup-deadline = "0" stays what it is — do not drain, end at once — and a program built that way expires nothing and exits as its code decides. That is a decision visible in the configuration. A key that turns an expired deadline back into success would let an operator declare data loss acceptable in a file the program’s author never sees, and there is none.

3. Consequences

4. What this does not decide

5. What is built

Steps 2 and 3. An expired deadline ends the program with exit status 70 and says so on the panic path: nikaia_std::rt::expired raises a panic with the message — so the program’s panic hook runs, which is D2 — catches the unwind, and exits EXIT_CLEANUP_EXPIRED. Standard output is never written to. cleanup-deadline = "0" does not drain and therefore never expires, which is D3 and needed no code. Part III 13.3b’s comment and Appendix A’s row say it, and an_expired_cleanup_deadline_is_exit_70_on_the_panic_path runs a program whose deadline expires and reads both the status and standard error — it has to be a second process, because a status is only a status once the process is over.

Step 1 is ADR-006’s and is not built, which bounds what the message can say: the parked-cleanup queue D3 describes does not exist, so what expires today is the drain of pending I/O operations and the message counts those rather than naming resources. When the queue lands, the names go in the same message on the same path.

What panic = "abort" costs is the status and not the message. There the process is gone before the exit code can be set, with the abort’s own status; the hook has already run and said what happened. It is named here rather than left to be discovered.

The order of work:

  1. the parked-cleanup queue and the drain, which are ADR-006’s — left;
  2. on expiry: the message through the panic hook’s path and exit status 70 — built;
  3. Part III 13.3b’s comment and Appendix A’s table, and a test that expires a deadline and reads the status — built.

#188 carries the remainder.

This site is open source. Improve this page.