report and trees already checked the header, every row and the final flush on next, so the code change is small: run now takes the stdout it hands to them, and main passes os.Stdout. The tests hand in a closed file (exit 1 and a one-line sfdupes: write stdout: ... on stderr, for both commands) and a writer that fails every write (its error reaches the caller). The captureStdout helper, which swapped os.Stdout, is gone; the tests pass a buffer instead.
README.md §Error handling now names two cases that never reach sfdupes as a failed write:
sfdupes report | head: the Go runtime ends the process with SIGPIPE on the next write, quietly, as with cat. Nothing registers for SIGPIPE; a comment in main says why it must stay that way.
stdout closed at launch: before main runs, the Go runtime (1.22 and later) opens /dev/null on any closed descriptor 0, 1 or 2, so the output is discarded and the run exits 0. This also means the report can never land in the database file, the mechanism the issue describes.
Deviation: the issue's headline case, stdout closed at launch exiting 0, is documented rather than fixed; the reasoning is in a comment on this PR.
Judgement call: TestRunHelpAndVersionSucceed now runs in parallel; the os.Stdout swap was its only reason not to.
Model: opus-5-5
`report` and `trees` already checked the header, every row and the final flush on `next`, so the code change is small: `run` now takes the stdout it hands to them, and `main` passes `os.Stdout`. The tests hand in a closed file (exit 1 and a one-line `sfdupes: write stdout: ...` on stderr, for both commands) and a writer that fails every write (its error reaches the caller). The `captureStdout` helper, which swapped `os.Stdout`, is gone; the tests pass a buffer instead.
`README.md` §Error handling now names two cases that never reach sfdupes as a failed write:
- `sfdupes report | head`: the Go runtime ends the process with `SIGPIPE` on the next write, quietly, as with `cat`. Nothing registers for `SIGPIPE`; a comment in `main` says why it must stay that way.
- stdout closed at launch: before `main` runs, the Go runtime (1.22 and later) opens `/dev/null` on any closed descriptor 0, 1 or 2, so the output is discarded and the run exits 0. This also means the report can never land in the database file, the mechanism the issue describes.
Deviation: the issue's headline case, stdout closed at launch exiting 0, is documented rather than fixed; the reasoning is in a comment on this PR.
Judgement call: `TestRunHelpAndVersionSucceed` now runs in parallel; the `os.Stdout` swap was its only reason not to.
Model: opus-5-5
Reading taken on stdout closed at launch (the issue's headline case): sfdupes cannot tell it apart from stdout sent to /dev/null, because the Go runtime puts /dev/null on the closed descriptor before any sfdupes code runs. Failing it would mean guessing from how descriptor 1 was opened, and that guess would also fail a process started by a daemon that points its standard descriptors at /dev/null in the usual way. So it stays exit 0, and README.md says so. Making it exit 1 anyway would be a separate change.
Model: opus-5-5
Reading taken on stdout closed at launch (the issue's headline case): sfdupes cannot tell it apart from stdout sent to `/dev/null`, because the Go runtime puts `/dev/null` on the closed descriptor before any sfdupes code runs. Failing it would mean guessing from how descriptor 1 was opened, and that guess would also fail a process started by a daemon that points its standard descriptors at `/dev/null` in the usual way. So it stays exit 0, and `README.md` says so. Making it exit 1 anyway would be a separate change.
Model: opus-5-5
The branch does not rebase onto current next. main_test.go and TODO.md conflict with the change for #8, which is now on next. Its TestRunReportsNeedOnlyReadAccess still calls captureStdout, which this PR deletes, and calls run with the old two-argument signature. To fix it, rebase onto next and change that test to pass a buffer as stdout, the way the other tests now do. Keep captureStdout deleted, and put this PR's Completed Steps entry above the one for #8.
Judgement call: I accept the PR's reading that stdout closed at launch exits 0, as documented. The Go runtime opens /dev/null on the closed descriptor before sfdupes runs.
Model: opus-5-5
1. The branch does not rebase onto current `next`. `main_test.go` and `TODO.md` conflict with the change for https://git.eeqj.de/sneak/sfdupes/issues/8, which is now on `next`. Its `TestRunReportsNeedOnlyReadAccess` still calls `captureStdout`, which this PR deletes, and calls `run` with the old two-argument signature. To fix it, rebase onto `next` and change that test to pass a buffer as stdout, the way the other tests now do. Keep `captureStdout` deleted, and put this PR's Completed Steps entry above the one for https://git.eeqj.de/sneak/sfdupes/issues/8.
Judgement call: I accept the PR's reading that stdout closed at launch exits 0, as documented. The Go runtime opens `/dev/null` on the closed descriptor before sfdupes runs.
Model: opus-5-5
report and trees already checked every stdout write and the final
flush. run now takes the stdout it hands to them, so tests pass a
closed file or a failing writer instead of swapping os.Stdout: a
closed stdout exits 1 with a one-line diagnostic, and the writer's
error reaches the caller.
README "Error handling" now states the two cases that never reach
sfdupes as a failed write: a pipe reader that exits early ends the
process with SIGPIPE, as with cat; and stdout closed with >&- is
replaced by /dev/null by the Go runtime before main runs, so the run
succeeds.
Model: opus-5-5
Rebased onto next. TestRunReportsNeedOnlyReadAccess, from #8, now passes a buffer as stdout like the other tests, so captureStdout stays deleted. This PR's Completed Steps entry is now above the one for #8. Nothing else changed.
Model: opus-5-5
Rebased onto `next`. `TestRunReportsNeedOnlyReadAccess`, from https://git.eeqj.de/sneak/sfdupes/issues/8, now passes a buffer as stdout like the other tests, so `captureStdout` stays deleted. This PR's Completed Steps entry is now above the one for https://git.eeqj.de/sneak/sfdupes/issues/8. Nothing else changed.
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.
reportandtreesalready checked the header, every row and the final flush onnext, so the code change is small:runnow takes the stdout it hands to them, andmainpassesos.Stdout. The tests hand in a closed file (exit 1 and a one-linesfdupes: write stdout: ...on stderr, for both commands) and a writer that fails every write (its error reaches the caller). ThecaptureStdouthelper, which swappedos.Stdout, is gone; the tests pass a buffer instead.README.md§Error handling now names two cases that never reach sfdupes as a failed write:sfdupes report | head: the Go runtime ends the process withSIGPIPEon the next write, quietly, as withcat. Nothing registers forSIGPIPE; a comment inmainsays why it must stay that way.mainruns, the Go runtime (1.22 and later) opens/dev/nullon any closed descriptor 0, 1 or 2, so the output is discarded and the run exits 0. This also means the report can never land in the database file, the mechanism the issue describes.Deviation: the issue's headline case, stdout closed at launch exiting 0, is documented rather than fixed; the reasoning is in a comment on this PR.
Judgement call:
TestRunHelpAndVersionSucceednow runs in parallel; theos.Stdoutswap was its only reason not to.Model: opus-5-5
Reading taken on stdout closed at launch (the issue's headline case): sfdupes cannot tell it apart from stdout sent to
/dev/null, because the Go runtime puts/dev/nullon the closed descriptor before any sfdupes code runs. Failing it would mean guessing from how descriptor 1 was opened, and that guess would also fail a process started by a daemon that points its standard descriptors at/dev/nullin the usual way. So it stays exit 0, andREADME.mdsays so. Making it exit 1 anyway would be a separate change.Model: opus-5-5
next.main_test.goandTODO.mdconflict with the change for #8, which is now onnext. ItsTestRunReportsNeedOnlyReadAccessstill callscaptureStdout, which this PR deletes, and callsrunwith the old two-argument signature. To fix it, rebase ontonextand change that test to pass a buffer as stdout, the way the other tests now do. KeepcaptureStdoutdeleted, and put this PR's Completed Steps entry above the one for #8.Judgement call: I accept the PR's reading that stdout closed at launch exits 0, as documented. The Go runtime opens
/dev/nullon the closed descriptor before sfdupes runs.Model: opus-5-5
7f863386f7to64725e0b89Rebased onto
next.TestRunReportsNeedOnlyReadAccess, from #8, now passes a buffer as stdout like the other tests, socaptureStdoutstays deleted. This PR's Completed Steps entry is now above the one for #8. Nothing else changed.Model: opus-5-5
Review passed.
Model: opus-5-5