Restore on SIGTERM, not just SIGINT; record the mutation sweep as done

The harness trapped SIGINT and nothing else, so a run wrapped in `timeout`
was killed with SIGTERM straight past the handler — leaving an INVERTED
fingerprint comparison in js/packages/cli/src/api/routes.ts along with the
lock file. That happened during this sweep. The lock is exactly what it is
for and it worked: the mutant was spotted and reverted before anything was
committed. But the restore should have run, so SIGTERM and SIGHUP now go
through the same handler. Verified by killing a live run and confirming the
tree came back clean.

Full verification sweep across all six targets, run after every fix: 132
mutants, 87 killed, 45 non-compiling, ZERO survivors.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
thePR0M3TH3ANandClaude Opus 5 committed 2026-08-19 19:24:39 -04:00
1 parent e1ea9c2b48
commit f043c95672
2 files changed
+38 -6

No files matched your search

+12 -3
View File
@@ -295,9 +295,18 @@ def main() -> int:
def restore(*_: object) -> None:
path.write_text(original)
signal.signal(
signal.SIGINT, lambda *a: (restore(), release_lock(), sys.exit(130))
)
# SIGTERM as well as SIGINT. Handling only Ctrl-C was not enough: a
# run wrapped in `timeout` is killed with SIGTERM, which skipped the
# handler entirely and left an INVERTED SECURITY CHECK in the working
# tree along with the lock file. The lock caught it, which is what it
# is for -- but the restore should have run in the first place.
def on_signal(*_: object) -> None:
restore()
release_lock()
sys.exit(130)
for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP):
signal.signal(sig, on_signal)
try:
for idx, mutant in enumerate(mutants, start=args.offset + 1):