Skip to content

Commit 84daafa

Browse files
Fix terminal snapshot corruption handling (#16)
Co-authored-by: rokt-clayschubiner <clay.schubiner@rokt.com>
1 parent 1b08dff commit 84daafa

1 file changed

Lines changed: 21 additions & 1 deletion

File tree

src/main/services/TerminalSnapshotService.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ async function readSnapshotFile(filePath: string): Promise<StoredSnapshot | null
6262
error,
6363
bytes: Buffer.byteLength(raw, 'utf8'),
6464
});
65+
await removeFile(filePath);
6566
return null;
6667
}
6768
}
@@ -76,6 +77,25 @@ async function removeFile(filePath: string): Promise<void> {
7677
}
7778
}
7879

80+
async function atomicWriteFile(filePath: string, contents: string): Promise<void> {
81+
const dir = path.dirname(filePath);
82+
const base = path.basename(filePath);
83+
const tmpPath = path.join(dir, `.${base}.${process.pid}.${Date.now()}.tmp`);
84+
await fs.promises.writeFile(tmpPath, contents, 'utf8');
85+
try {
86+
await fs.promises.rename(tmpPath, filePath);
87+
} catch (error) {
88+
const code = (error as NodeJS.ErrnoException)?.code;
89+
if (code === 'EEXIST' || code === 'EPERM') {
90+
await fs.promises.rm(filePath, { force: true });
91+
await fs.promises.rename(tmpPath, filePath);
92+
return;
93+
}
94+
await fs.promises.rm(tmpPath, { force: true });
95+
throw error;
96+
}
97+
}
98+
7999
async function listSnapshots(): Promise<
80100
Array<{ id: string; path: string; stats: StoredSnapshot }>
81101
> {
@@ -121,7 +141,7 @@ class TerminalSnapshotService {
121141
}
122142

123143
await ensureDir();
124-
await fs.promises.writeFile(snapshotPath(id), json, 'utf8');
144+
await atomicWriteFile(snapshotPath(id), json);
125145
await this.pruneIfNeeded(id);
126146
return { ok: true };
127147
} catch (error) {

0 commit comments

Comments
 (0)