Date: 2026-09-28. Two reviewers (signals and concurrency; error paths), two skeptics per finding. 7 findings, all confirmed; no data loss. Raw results: ~/smartconfig-work/review-r4-results.json on the VM; the reviewers’ reproduction scripts are in ~/smartconfig-work/review-r4/.
| Lens | Finding | Reviewer | Skeptics |
|---|---|---|---|
| signals 1 | A second signal bypasses the safe point: after the rename, sc exits mid-COMMIT and says only ‘interrupted’ (logic 1 / lifecycle 1 come back through a double Ctrl-C) | medium | low, low |
| signals 2 | A signal just after a snapshot/restore finished makes sc exit 1 with ‘interrupted’ although the change was made and recorded | low | low, low |
| signals 3 | Ctrl-C no longer stops a shell loop: sc exits 1 instead of dying by SIGINT, so bash runs the next restore | medium | medium, medium |
| signals 4 | kill -ABRT (and TRAP/SYS/STKFLT, or kill-sent ILL/BUS/SEGV/FPE) still prints a 220-line Go stack trace and leaves the temp file in the config directory | low | low, low |
| paths 1 | A second signal after the rename exits with only ‘interrupted by X’: the file is replaced, no restore row is kept and the undo id is not named, and one ‘systemctl stop’ is enough to cause it | medium | medium, medium |
| paths 2 | Busy() drops before sc reports the result, so a signal after Restore returns overrides the report: exit 1 after a completed restore, contradictory lines, or the undo id lost | low | low, low |
| paths 3 | The MILESTONES and handleSignals contract says ‘exits 1’ after a signal, but a signal after the last safe point finishes the operation and exits 0 | low | low, low |
sc exit on its own with a message that could contradict the result (signals 2, paths 2).sc keeps waiting (bounded: after the first signal it makes at most one more COMMIT attempt) and reports the truth (signals 1, paths 1).sc ends by the signal it received (SIGINT, SIGTERM, SIGHUP), so a shell loop stops like it does for any other program; SIGQUIT and the other stop signals end with exit status 1 (signals 3). This is a deliberate exception to “exit 1 on any error”: an interrupt is the user’s request, and shells rely on it.sc snapshot interrupted while it waits for the lock: exit 1 with ‘snapshot P: interrupted (nothing recorded)’ and no row added (bin/snapB.sh, case 1).The reviewers left their scripts in ~/smartconfig-work/review-r4/signals/bin
on the VM. They were run against the round-4 binary they reviewed and the
round-5 binary (151b7fd):
| Scenario (script) | Round 4 | Round 5 |
|---|---|---|
One Ctrl-C after the rename, COMMIT held up by a reader (double.sh 1) |
truthful line, exit 1 | truthful line naming the saved id, ends by SIGINT (130) |
Two Ctrl-C after the rename (double.sh 2) |
only “interrupted by interrupt”, exit 1 after 0.3 s | waits for the one remaining COMMIT attempt (about 5 s), then the truthful line naming the saved id, ends by SIGINT; 3 of 3 runs |
Signal after Restore returned, before the result line (raceB.sh) |
“interrupted”, exit 1, no result printed | result line printed, then ends by SIGINT |
SIGABRT / SIGTRAP while waiting for the lock (othersig.sh) |
exit 2, 220-line stack trace, temp file left in the config dir | one line, exit 1, no stack trace, no temp file |
| Ctrl-C in a bash loop | loop runs its next step | loop stops (repo test TestCtrlCStopsShellLoop) |