fix(output): write reports atomically - #1160
Conversation
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 1 selected item(s). |
| return w.closeErr | ||
| } | ||
| w.file = nil | ||
| if err := os.Rename(w.tempPath, w.commitPath); err != nil { |
There was a problem hiding this comment.
| if err := os.Rename(w.tempPath, w.commitPath); err != nil { | |
| if err := replaceOutputFile(w.tempPath, w.commitPath); err != nil { |
There was a problem hiding this comment.
Thanks. replaceOutputFile doesn't currently exist in this package or elsewhere in the repository. Is there a specific implementation or platform behavior you want it to provide beyond os.Rename? Go's Windows implementation calls MoveFileEx with MOVEFILE_REPLACE_EXISTING, and TestLazyFileWriter_ReplacesExistingFileOnlyOnClose passes in the current Windows CI job. I can add a wrapper if it serves a concrete semantic or testing purpose, but I don't want to introduce a pass-through helper based on the name alone.
|
The temp+rename approach is right, but |
|
Thanks for catching the defer ordering. Fixed in
Validation:
|
Description
lazyFileWritercurrently truncates an existing--outputfile as soon as the first byte is written. A write failure or forced termination during rendering can therefore leave JSON/SARIF consumers with a partial document at a path that previously held a complete report.This change restores the old-or-new file guarantee by:
os.Create-style permissions for new files and the mode of an existing target;Results written toonly after the rename succeeds.The cancellation policy is unchanged: a successfully rendered cancelled-run report can still replace an older report. This PR only addresses atomic persistence.
Type of Change
How Has This Been Tested?
make testpasses locallyValidated on Windows/amd64 with Go 1.25.5:
make fmtmake checkmake test(full race-enabled suite)make buildmake coverage(90.5% total; 90% threshold)ocr review --preview --format json --outputreplacement, including JSON parsing and success-hint verificationClose; the previous target remained byte-for-byte intact and only the identifiable.ocr-out-*temporary file contained the uncommitted bytesTests cover commit-on-close, replacement timing, new/existing file modes, rename failure, injected write failure, symlink preservation, ANSI stripping, unchanged JSON bytes, and temporary-file cleanup.
Checklist
go fmt,go vet)Related Issues
Addresses item 1 of #1141.