Skip to content

fix: report a failed -o write instead of claiming success - #748

Open
VXNCXNX wants to merge 1 commit into
boyter:masterfrom
VXNCXNX:fix/output-write-error
Open

fix: report a failed -o write instead of claiming success#748
VXNCXNX wants to merge 1 commit into
boyter:masterfrom
VXNCXNX:fix/output-write-error

Conversation

@VXNCXNX

@VXNCXNX VXNCXNX commented Aug 15, 2026

Copy link
Copy Markdown

I explicitly licence this contribution under the MIT licence.

What's broken

-o reports success after a write that failed, and exits 0.

$ scc --format json -o /nonexistentdir/z.json .
results written to /nonexistentdir/z.json
$ echo $?
0
$ ls /nonexistentdir/z.json
ls: cannot access '/nonexistentdir/z.json': No such file or directory

--format-multi has the same hole, exiting 0 with the message going to stdout:

$ scc --format-multi 'json:/nonexistentdir/m.json' . ; echo $?
0

The same mistakes on the sibling paths are already handled, which is what makes this look like an oversight:

$ scc --hotspots --depth 20 -o /nonexistentdir/x.json . ; echo $?
open /nonexistentdir/x.json: no such file or directory
1
$ scc --report=/nonexistentdir/r.html . ; echo $?
create report file: open /nonexistentdir/r.html: no such file or directory
1

Read-only directories and paths that are actually directories fail the same silent way. In a CI job that is a green build with no artifact.

The fix

processor.go had _ = os.WriteFile(...) followed unconditionally by the success line. It now checks the error, prints it to stderr and exits 1, following runReport a few hundred lines above, which does exactly this. formatters.go already built a message for the multi case but sent it to stdout and carried on, so it goes to stderr and exits too.

After:

$ scc --format json -o /nonexistentdir/z.json . ; echo $?
open /nonexistentdir/z.json: no such file or directory
1
$ scc --format-multi 'json:/nonexistentdir/m.json' . ; echo $?
/nonexistentdir/m.json unable to be written to for format json: open /nonexistentdir/m.json: no such file or directory
1

A successful write is unchanged, still results written to ... and exit 0.

os.Exit(1) rather than returning an error, because Process() is called from cobra's Run:, which has no error return, and the report path at processor.go:932 already resolves that the same way. Happy to plumb an error through instead if you would rather change the signature.

This changes an exit code, from 0 to 1, on a case that previously failed silently. That is the point of the change, but it is a visible difference for anyone whose script currently ignores it.

Verification

TestRegressionOutputWriteFailureExits in regression_test.go, one subtest per path. It observes the real exit code: runSCCDir runs the test binary as a subprocess, so os.Exit(1) is a genuine child-process exit. It asserts non-zero exit, absence of the success message, presence of the reason, and that no file was created. With both source changes reverted and the test kept, both subtests fail.

LANG=C go test ./... passes.

A note in case you see it too: on a machine with a comma-decimal LANG, four unrelated tests fail on master, because the formatters build their printer from os.Getenv("LANG") while the fixtures spell numbers with a dot. LANG=C is green before and after.

On issue #510

I looked at whether this explains #510 and I do not think it does. That reporter got the tabular summary written instead of JSON, which is the unknown-format fallback from a json2-less binary, matching your reply that the action needed updating. Their target directory was writable, so no write failed and nothing was swallowed. Same family of symptom, different cause.

File write failures for -o and --format-multi now exit with status 1 and report errors to stderr, matching the behavior of --report, --hotspots, and other output paths. Adds regression test to verify this contract.
@pr-insights pr-insights Bot added M/complexity Normal or medium complexity M/size Normal or medium sized change labels Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

M/complexity Normal or medium complexity M/size Normal or medium sized change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant