There is no signal handling anywhere in the repo — os/signal is never imported. runScan (scan.go:53-78) installs no handler, the walk dispatcher and hash worker pools (scan.go:373, scan.go:590-613) have no cancellation path, and progress.go never finishes the bar or restores the terminal line.
The headline use case is a multi-hour scan of ~10M files / ~150 TB, run from cron or interactively. An operator who interrupts at hour three kills the process mid-render: no final summary, no clean terminal, no controlled commit of the in-flight batch. Batched commits mean most work survives, but the shutdown is uncontrolled and undocumented.
Definition of done
scan installs signal.NotifyContext for SIGINT and SIGTERM; the resulting ctx is threaded as the first argument through the walk dispatcher, the hash pool and the database writes, per the Go styleguide.
On cancellation the pools drain promptly, the in-flight batch is committed (not discarded — partial progress is the documented contract), and the database is closed.
The active progress display is finished so the terminal is left clean.
A scan: interrupted after N files line goes to stderr and the process exits 1. README §Error handling documents the interrupt behaviour and the exit code.
A test calls the scan entrypoint with an already-cancelled context and asserts it returns promptly, leaves a valid database, and that a subsequent scan converges normally.
make check green.
Depends on #4 (the database must actually close) and overlaps #6 (the pools must be cancellable).
There is no signal handling anywhere in the repo — `os/signal` is never imported. `runScan` (`scan.go:53-78`) installs no handler, the walk dispatcher and hash worker pools (`scan.go:373`, `scan.go:590-613`) have no cancellation path, and `progress.go` never finishes the bar or restores the terminal line.
The headline use case is a multi-hour scan of ~10M files / ~150 TB, run from cron or interactively. An operator who interrupts at hour three kills the process mid-render: no final summary, no clean terminal, no controlled commit of the in-flight batch. Batched commits mean most work survives, but the shutdown is uncontrolled and undocumented.
## Definition of done
1. `scan` installs `signal.NotifyContext` for SIGINT and SIGTERM; the resulting `ctx` is threaded as the first argument through the walk dispatcher, the hash pool and the database writes, per the Go styleguide.
2. On cancellation the pools drain promptly, the in-flight batch is committed (not discarded — partial progress is the documented contract), and the database is closed.
3. The active progress display is finished so the terminal is left clean.
4. A `scan: interrupted after N files` line goes to stderr and the process exits 1. README §Error handling documents the interrupt behaviour and the exit code.
5. A test calls the scan entrypoint with an already-cancelled context and asserts it returns promptly, leaves a valid database, and that a subsequent scan converges normally.
6. `make check` green.
Depends on #4 (the database must actually close) and overlaps #6 (the pools must be cancellable).
clawbot
added this to the 1.0.0 milestone 2026-08-09 03:43:40 +02:00
Plan (implementer's brief). Dispatched after #13 (progress display) and #53 (scan lock) land. Cancellation of the worker pools is already in place on next; this unit adds the signals and the clean finish.
The scan command wraps its context with signal.NotifyContext for SIGINT and SIGTERM. After the first signal, restore the default handling so a second Ctrl-C ends the process at once; say so in README.md. Never register for SIGPIPE (#30 relies on the default).
Trap 1: on interrupt the walk is incomplete, so the update phase must not delete records the scan has not verified. An interrupted scan writes the hash results it already has and deletes nothing. Test it: records under a path the interrupted scan never reached survive.
Trap 2: the batch must be committed after the context is cancelled, so that commit cannot use the cancelled context; use context.WithoutCancel (or the plainest equivalent) for it, and only for it.
The progress display is finished, then one line scan: interrupted after N files (N: files walked so far) goes to stderr, and the exit code is 1. The scan lock is released and the database closed on this path like any other.
README.md §Error handling documents interrupts: what is kept, what is not, the exit code.
Tests per the definition of done.
Model: opus-5-5
Plan (implementer's brief). Dispatched after https://git.eeqj.de/sneak/sfdupes/issues/13 (progress display) and https://git.eeqj.de/sneak/sfdupes/issues/53 (scan lock) land. Cancellation of the worker pools is already in place on `next`; this unit adds the signals and the clean finish.
- The scan command wraps its context with `signal.NotifyContext` for SIGINT and SIGTERM. After the first signal, restore the default handling so a second Ctrl-C ends the process at once; say so in `README.md`. Never register for SIGPIPE (https://git.eeqj.de/sneak/sfdupes/issues/30 relies on the default).
- Trap 1: on interrupt the walk is incomplete, so the update phase must not delete records the scan has not verified. An interrupted scan writes the hash results it already has and deletes nothing. Test it: records under a path the interrupted scan never reached survive.
- Trap 2: the batch must be committed after the context is cancelled, so that commit cannot use the cancelled context; use `context.WithoutCancel` (or the plainest equivalent) for it, and only for it.
- The progress display is finished, then one line `scan: interrupted after N files` (N: files walked so far) goes to stderr, and the exit code is 1. The scan lock is released and the database closed on this path like any other.
- `README.md` §Error handling documents interrupts: what is kept, what is not, the exit code.
- Tests per the definition of done.
Model: opus-5-5
clawbot
self-assigned this 2026-10-03 14:11:59 +02:00
Built in #79. A first SIGINT or SIGTERM now stops scan cleanly: it commits the hashed records still waiting in its batch, starts no other write or deletion, finishes the progress display, prints scan: interrupted after N files and exits 1. A second signal ends it at once. Two judgement calls are disclosed on the PR: a SIGINT inherited as ignored stays ignored, and an interrupt while the database opens also reports as interrupted.
Model: opus-5-5
Built in https://git.eeqj.de/sneak/sfdupes/pulls/79. A first SIGINT or SIGTERM now stops `scan` cleanly: it commits the hashed records still waiting in its batch, starts no other write or deletion, finishes the progress display, prints `scan: interrupted after N files` and exits 1. A second signal ends it at once. Two judgement calls are disclosed on the PR: a SIGINT inherited as ignored stays ignored, and an interrupt while the database opens also reports as interrupted.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
There is no signal handling anywhere in the repo —
os/signalis never imported.runScan(scan.go:53-78) installs no handler, the walk dispatcher and hash worker pools (scan.go:373,scan.go:590-613) have no cancellation path, andprogress.gonever finishes the bar or restores the terminal line.The headline use case is a multi-hour scan of ~10M files / ~150 TB, run from cron or interactively. An operator who interrupts at hour three kills the process mid-render: no final summary, no clean terminal, no controlled commit of the in-flight batch. Batched commits mean most work survives, but the shutdown is uncontrolled and undocumented.
Definition of done
scaninstallssignal.NotifyContextfor SIGINT and SIGTERM; the resultingctxis threaded as the first argument through the walk dispatcher, the hash pool and the database writes, per the Go styleguide.scan: interrupted after N filesline goes to stderr and the process exits 1. README §Error handling documents the interrupt behaviour and the exit code.make checkgreen.Depends on #4 (the database must actually close) and overlaps #6 (the pools must be cancellable).
Plan (implementer's brief). Dispatched after #13 (progress display) and #53 (scan lock) land. Cancellation of the worker pools is already in place on
next; this unit adds the signals and the clean finish.signal.NotifyContextfor SIGINT and SIGTERM. After the first signal, restore the default handling so a second Ctrl-C ends the process at once; say so inREADME.md. Never register for SIGPIPE (#30 relies on the default).context.WithoutCancel(or the plainest equivalent) for it, and only for it.scan: interrupted after N files(N: files walked so far) goes to stderr, and the exit code is 1. The scan lock is released and the database closed on this path like any other.README.md§Error handling documents interrupts: what is kept, what is not, the exit code.Model: opus-5-5
Built in #79. A first SIGINT or SIGTERM now stops
scancleanly: it commits the hashed records still waiting in its batch, starts no other write or deletion, finishes the progress display, printsscan: interrupted after N filesand exits 1. A second signal ends it at once. Two judgement calls are disclosed on the PR: a SIGINT inherited as ignored stays ignored, and an interrupt while the database opens also reports as interrupted.Model: opus-5-5