Repository navigation
Open
Description
Problem
- Measured (main 2fb1779). Six write-to-temp-then-rename sites, each with a bare
fs.renameSyncand no retry:project-remap.js:24-25(JSONL rewrite on remap; on error unlinks the temp and throws)remote-mirror.js:50-52(writeManifest, fixed.tmpname, no catch)remote-transport.js:357(scp fetch) and:412(range append) — on error: temp removed,log.warn, fetch counted failedschedule-runner.js:656(setScheduleEnabled,renameinjectable, returns{ ok:false })trigger-watcher.js:1080(result file; on errorsafeLogError, the result is lost)- (
session-index-store.js:28-30is a one-shot legacy DB relocation, not an atomic write; out of scope.)
- Assumed (Windows behaviour, not reproduced here).
MoveFileExonto an existing file fails withEPERM/EACCES/EBUSYwhile another process holds the destination open withoutFILE_SHARE_DELETE(Defender, the search indexer, an editor, a non-Node reader of a trigger result). Node's own readers share delete and do not cause it.graceful-fs(already innode_modulestransitively) retries such renames for this reason. - Measured: no occurrence logged.
main.log+main.old.log(2026-10-27 21:44 → 2026-10-29 23:55) contain 0EPERM/EBUSY. The risk is real in principle, its frequency unknown.
Scope
- One
atomic-write.jsexportingrenameWithRetry(src, dest, { attempts, delays, sleep, rename })(sync, since all callers are sync; bounded backoff, e.g. 10/30/100/300 ms, total < 0.5 s; retry onlyEPERM,EACCES,EBUSYon win32; other codes and other platforms rethrow at once) and optionallywriteFileAtomic(dest, data, opts)with a unique temp name (remote-mirror.jsreuses a fixed.tmp). - Route the six sites through it; keep each caller's existing error handling.
- Context note in
.ai/contexts/(session-cache or trigger-watcher, where the sites are documented).
Out of scope
- Async rewrites of the callers; the legacy DB relocation; changing what callers do after a final failure.
Acceptance
- Red first: unit test with an injected
renamethat throwsEPERMtwice then succeeds → the call returns andrenamewas called 3 times; withENOENT→ throws after 1 call; with persistentEPERM→ throws the last error after the bounded attempts. Fails today (module absent). - A Windows-only integration test holds the destination open with a non-sharing handle from a child process the test spawns, releases it after 50 ms, and checks the rename lands.
- Each of the six sites has a test proving it uses the helper (injected rename or module stub).
Notes
- Sync sleep:
Atomics.waiton aSharedArrayBuffer, never a busy loop; the main-process callers block the event loop for at most the bound. - Tests write only in their own temp dirs, never in
~/.switchboardor the triggers dir of the user's running Switchboard, and never kill anything; if a helper process is needed, stop it by its PID, never by image name. - Related: audit 2026-10-29 recommendation 6 (it counted 5 sites; 6 found).
Metadata
Metadata
Assignees
Labels
No labels
Activity
(fs): retry atomic renames on Windows EPERM/EBUSY/EACCES through one …
(fs): retry atomic renames on Windows EPERM/EBUSY/EACCES through one …