fix(directory): atomic file writes via temp + rename #26
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
basepeak-ai/workspace-provider!26
Loading…
Reference in a new issue
No description provided.
Delete branch "atomic-directory-write"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The
directoryworkspace provider wrote files viaos.OpenFile(..., O_TRUNC)+io.Copy(pkg/client/directory.go). The target was truncated to zero before the new content streamed in, so a process killed mid-write left a truncated/partial file. For knowledge data sources this corrupted.metadata.json, and the corrupt file then aborted the next sync.Change
directoryProvider.writeFilenow writes atomically:safeopen.OpenFileBeneath(preserving the path-traversal guard) with anO_EXCL+ uuid name.os.Renameover the target — atomic on POSIX within one filesystem, so a reader sees either the complete old file or the complete new file, never a torn one.Temp files use a reserved
.wsp-tmp.prefix that the recursivelsskips, so an in-flight (or crash-leftover) temp never appears in a listing.s3/azureproviders already write atomically via a single upload and are untouched.This is an unconditional fix — no new API or flag. Every directory-provider write (including revision snapshots) becomes atomic transparently.
Notes / tradeoffs
os.Renameover an existing file is not atomic on Windows; deployment is Linux (documented in a code comment)..wsp-tmp.<uuid>file. It is hidden from listings by thels-skip and reclaimed on workspace removal — accepted tradeoff, no reaper added.Tests
TDD: a new
TestWriteFile_AtomicNoTornFileOnErrorfails against the oldO_TRUNCcode (a mid-write reader error leaves a torn file) and passes after the change (original content survives).TestLs_SkipsTempFilescovers the listing skip.go build ./...,go vet ./..., and the fullpkg/clientsuite pass.Related
.metadata.jsonhardening (throttled writes + rebuild-on-corrupt) — the tool-side counterpart.a98cd8791c35006f4290Finalize review summary
Fresh-context review (code-reviewer subagent) + local CI parity (gofmt/vet/
go test -count=1/build/golangci-lint — all green, 0 issues).Fixed
TestLs_SkipsTempFilesnow cleans up its seededreal.txtand.wsp-tmp.leftoverviat.Cleanup, so it can't leak into sibling tests that list the shared workspace root.Dismissed (with reason)
tmpon theio.Copyerror path" — false positive: the firsttmp.Close()is inside theif err != nilbranch and is immediately followed byreturn err; it never coexists with the success-path close. No double-close path exists.MkdirAllbypasses safeopen's traversal guard" — pre-existing (not touched by this PR) and safe:filepath.Joincollapses..before the join, and the actual file open still goes throughsafeopen.OpenFileBeneath.Scope note
.gitignorechore commit initially rode along; rebased out so this PR is purely the atomic-write change (2 commits).