Date: 2026-10-03. One reviewer (an AI agent), on 1c616b6..d3032b2:
sc check, Store.Replace with the restore refactor, and sc edit. The
first three findings were reproduced with the built binary in a throwaway
SC_HOME; the rest come from reading the code. Claude reproduced or
confirmed each before fixing it.
Held up: the restore refactor (commitRestore into commitWrite,
safeDir into safeDirFor) keeps its behaviour and removes no guard.
| Area | Finding | Severity | Fixed in |
|---|---|---|---|
| sc edit | Any failure after the editor had run, other than “changed on disk”, deleted the edited copy: a locked store, a full disk, a failed check. Reproduced with an unwritable objects/. |
high | a946f6f (the copy is kept and its path printed) |
| sc edit | A SIGINT or SIGQUIT while the editor ran (Ctrl-C in a shell started from vim, EDITOR=ed) killed sc at once; the editor kept running and its work went to a copy the next sc edit deleted. Reproduced. |
high | a946f6f (left to the editor, as sudoedit and git do; leftovers are kept, not deleted) |
| store | For a new file, the absent state was recorded only when the path had no rows. After an unrecorded deletion the edit row followed a stale file row, so the creation could not be undone. Reproduced. | medium | a946f6f (a deleted row first) |
| sc check | With no argument, a file sc could not read was a note and the exit status stayed 0: sc check && deploy as a user would pass over an unread sudoers. |
medium | a946f6f (exit 1, one line on stderr) |
| sc check | An id had to be 4 to 64 hex digits, while sc cat takes 1 to 6 in any case: deadbeef and a file named cafe failed oddly, a 3-digit prefix was taken for a path. |
medium | a946f6f (an id when it looks like one and no such file exists) |
| sc edit | The file was not required to hold still while first read; a torn copy would only be refused at the save. | low | a946f6f (3 tries) |
| sc edit | The old content was checked before the editor opened: a wasted validator run for unchanged edits, and a stale baseline after a long edit. | low | a946f6f (checked after the editor, with the new content) |
| store | Replace could write a file larger than sc reads back (8 MB). |
low | a946f6f |
| check | Graph.Files walked whole subtrees for patterns that match one level. |
low | a946f6f |
| output | The summary line of sc check and the last line of sc edit were wider than 80 columns. |
low | a946f6f (two lines) |
TestEditKeepsWorkWhenSaveFails, TestEditLeavesCtrlCToTheEditor (the
real binary; a mutation that does not leave the signals to the editor
fails it), TestEditValidatorRuns, the end-of-input and leftover cases
of TestEditBlocker and TestEditFileChangedMeanwhile,
TestCheckCLIUncheckedFile, the id cases of TestCheckCLIByID, and the
unrecorded-deletion and size cases of the Replace tests. The retry of an
unstable first read has no test of its own.
TestWatchSIGHUPRescans (M2) timed out once in a root run under load:
it allows 10 s for a rescan that comes about 10 s after the start. It
passed 8 of 8 alone and in a full rerun. It joins the timing-bound tests
to make robust before chunk D.