fix(usage-log): write via temp file and atomic rename to prevent corruption
appendRecord previously called fs.writeFile(token-usage.json, …) directly. A process crash or SIGKILL during that write can leave the file truncated; readJsonArray then throws (SyntaxError), and since attempt.ts swallows the error with .catch(), that one interrupted write silently disables all future token logging for the workspace until the file is manually repaired. Fix: write the new content to a uniquely-named sibling temp file first, then call fs.rename() to atomically replace the real file. rename(2) is atomic on POSIX when src and dst share the same directory/filesystem, so readers always see either the old complete file or the new complete file — never a partial write. The temp file is unlinked on error to avoid leaving orphans.
This commit is contained in:
parent
1a5489bf32
commit
8c162d0ba4
@ -145,7 +145,18 @@ async function appendRecord(file: string, entry: TokenUsageRecord): Promise<void
|
||||
await withFileLock(lockPath, async () => {
|
||||
const records = await readJsonArray(file);
|
||||
records.push(entry);
|
||||
await fs.writeFile(file, JSON.stringify(records, null, 2));
|
||||
// Write to a sibling temp file then atomically rename into place so that
|
||||
// a crash or kill during the write never leaves token-usage.json truncated.
|
||||
// rename(2) is atomic on POSIX when src and dst are on the same filesystem,
|
||||
// which is guaranteed here because both paths share the same directory.
|
||||
const tmp = `${file}.tmp.${randomBytes(4).toString("hex")}`;
|
||||
try {
|
||||
await fs.writeFile(tmp, JSON.stringify(records, null, 2));
|
||||
await fs.rename(tmp, file);
|
||||
} catch (err) {
|
||||
await fs.unlink(tmp).catch(() => {});
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user