fix: a stopped run says so, and says what survived - #75
Merged
Conversation
Ctrl+C and a CI timeout both printed "tfg: context canceled" - six characters
of Go runtime vocabulary handed to somebody who had just told the tool to stop
and now wanted to know whether their directory was safe to reuse. The window
has said the useful thing since it had a progress bar. The command line was
the surface staying quiet.
tfg: stopped before it finished. 897 files written, and the manifest
describes exactly those.
Reproduced against a real signal in a Linux container, because a signal cannot
be delivered to this process from the shell on this machine - which is why
O175 was written from the code on 2026-09-02 and left open. SIGINT into a run
of 3000: exit 130, "897 files written", 897 on disk. SIGTERM: exit 143, "755
files written", 755 on disk. The count matches to the file.
Two sentences rather than three, and that is a decision. The report asked for
Ctrl+C and SIGTERM to read differently, since that is why their exit codes
differ. Both arrive here as context.Canceled - the signal is known only in
cmd/tfg/main.go, deliberately, because signal.NotifyContext does not say which
one arrived. Separating them in the words would mean plumbing the signal into
cli.Run, whose signature the whole guard suite is written against. The exit
code already carries it, and that is the channel a script reads. A deadline
gets its own sentence, because the error itself distinguishes it and that
costs nothing.
The guard reaches the sentence and not the count, and the code says so. The
command line plans before it runs and planning honours the context, so a run
started with a finished context returns from PlanContext and never reaches the
count. Landing between the two needs a clock, and a guard on a clock goes red
on a busy machine rather than on a defect.
Worth recording: the first version had a third guard, for a run that never
began claiming no count. It was green and could not be reddened, because that
path does not call whatSurvived at all. The mutation found it - NOT CAUGHT -
and the guard was removed rather than kept for comfort.
Two mutations, both caught.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stability report finding T6, which was already ours as O175 (2026-09-02, deliberately deferred pending your call). You said do it.
What was wrong
Ctrl+C and a CI timeout both printed:
Six characters of Go runtime vocabulary, handed to somebody who had just told the tool to stop and now wanted to know whether their directory was safe to reuse. The window has said the useful thing since it had a progress bar -
Stopped after N files. The manifest describes exactly those.The command line was the surface staying quiet about facts it already had.Reproduced with a real signal
O175 was written from the code, not from a run, because a signal cannot be delivered to this process from the shell on this machine -
kill -INTfrom MSYS does not deliver aCTRL_C_EVENT. Done in a Linux container instead:897 files written755 files writtenThe count matches to the file.
Two sentences rather than three, and that is a decision
The report asked for Ctrl+C and SIGTERM to read differently, since that is why their exit codes differ. Both arrive as
context.Canceled- the signal is known only incmd/tfg/main.go, deliberately, becausesignal.NotifyContextdoes not say which one arrived. Separating them in the words means plumbing the signal intocli.Run, whose signature the whole guard suite is written against.The exit code already carries it (130 against 143), and that is the channel a script reads. A person who pressed Ctrl+C does not need to be told they pressed Ctrl+C. A deadline gets its own sentence, because the error itself distinguishes it and that costs nothing.
What the guard proves, and what it does not
The guard reaches the sentence. It does not reach the count, and the code says so rather than leaving it to be discovered: the command line plans before it runs and planning honours the context, so a run started with a finished context returns from
PlanContextand never arrives at the count. Landing between the two needs a cancel timed after planning and before the last file - a clock, and a guard on a clock goes red on a busy machine rather than on a defect.The count is proven by running it, in the table above.
One thing worth recording
The first version had a third guard, asserting that a run which never began claims no count. It was green and could not be reddened - that path does not call
whatSurvivedat all. The mutation found it (NOT CAUGHT), not the reading. The guard was removed rather than kept for comfort, and the condition it was aimed at is documented as unreachable from this surface with the reason it stays.Two mutations, both caught.
preflight --quickgreen on all 12.🤖 Generated with Claude Code