feat(lib): fs-atomic — one atomic-write implementation, with the race actually fixed

Atomic tmp-write-then-rename was reimplemented ~20 times across lib/, bin/,
and browse/src with three tmp-suffix conventions. One of them was a latent
bug this commit closes: lib/worktree.ts used a bare '.tmp' suffix — the
deterministic-tmp collision race browse/src/server.ts documents having hit
in production (its fix, pid+random, was trapped in a comment at one site).

lib/fs-atomic.ts: atomicWriteSync (always throws, best-effort tmp cleanup,
pid+random suffix, optional mode applied at tmp creation so the file never
exists with looser permissions) + atomicWriteQuiet (shutdown paths only).
Unit tests pin the throw/quiet contracts, 0600 mode, tmp-name uniqueness
(captured via the read-only-dir failure path — Bun's fs exports are
readonly, no monkeypatching), and no-stray-tmp cleanup.

Migrated: lib/worktree.ts (the bare-.tmp bug), lib/gstack-decision.ts
(snapshot + compact log), lib/gbrain-local-status.ts (probe cache). browse
sites follow separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 21:12:21 -07:00
co-authored by Claude Fable 5
parent 408ee77cde
commit 3023216b87
5 changed files with 191 additions and 15 deletions
+4 -3
View File
@@ -13,6 +13,7 @@ import { spawnSync } from 'child_process';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import { atomicWriteSync } from './fs-atomic';
import * as os from 'os';
// --- Interfaces ---
@@ -84,9 +85,9 @@ function loadDedupIndex(): DedupIndex {
function saveDedupIndex(index: DedupIndex): void {
const dir = path.dirname(getDedupPath());
fs.mkdirSync(dir, { recursive: true });
const tmp = getDedupPath() + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(index, null, 2));
fs.renameSync(tmp, getDedupPath());
// Was a bare '.tmp' suffix — the deterministic-tmp collision race the
// shared helper exists to prevent.
atomicWriteSync(getDedupPath(), JSON.stringify(index, null, 2));
}
// --- WorktreeManager ---