Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/node/services/historyService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "fs/promises";
import * as path from "path";
import writeFileAtomic from "write-file-atomic";
import type { Result } from "@/common/types/result";
import { Ok, Err } from "@/common/types/result";
import type { MuxMessage } from "@/common/types/message";
Expand Down Expand Up @@ -235,7 +236,8 @@ export class HistoryService {
.map((msg) => JSON.stringify({ ...msg, workspaceId }) + "\n")
.join("");

await fs.writeFile(historyPath, historyEntries);
// Atomic write prevents corruption if app crashes mid-write
await writeFileAtomic(historyPath, historyEntries);
return Ok(undefined);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Expand Down Expand Up @@ -272,7 +274,8 @@ export class HistoryService {
.map((msg) => JSON.stringify({ ...msg, workspaceId }) + "\n")
.join("");

await fs.writeFile(historyPath, historyEntries);
// Atomic write prevents corruption if app crashes mid-write
await writeFileAtomic(historyPath, historyEntries);

// Update sequence counter to continue from where we truncated
if (truncatedMessages.length > 0) {
Expand Down Expand Up @@ -399,7 +402,8 @@ export class HistoryService {
.map((msg) => JSON.stringify({ ...msg, workspaceId }) + "\n")
.join("");

await fs.writeFile(historyPath, historyEntries);
// Atomic write prevents corruption if app crashes mid-write
await writeFileAtomic(historyPath, historyEntries);

// Update sequence counter to continue from where we are
if (remainingMessages.length > 0) {
Expand Down Expand Up @@ -455,7 +459,8 @@ export class HistoryService {
.map((msg) => JSON.stringify({ ...msg, workspaceId: newWorkspaceId }) + "\n")
.join("");

await fs.writeFile(newHistoryPath, historyEntries);
// Atomic write prevents corruption if app crashes mid-write
await writeFileAtomic(newHistoryPath, historyEntries);

// Transfer sequence counter to new workspace ID
const oldCounter = this.sequenceCounters.get(oldWorkspaceId) ?? 0;
Expand Down
5 changes: 4 additions & 1 deletion src/node/services/partialService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "fs/promises";
import * as path from "path";
import writeFileAtomic from "write-file-atomic";
import type { Result } from "@/common/types/result";
import { Ok, Err } from "@/common/types/result";
import type { MuxMessage } from "@/common/types/message";
Expand Down Expand Up @@ -80,7 +81,9 @@ export class PartialService {
},
};

await fs.writeFile(partialPath, JSON.stringify(partialMessage, null, 2));
// Atomic write: writes to temp file then renames, preventing corruption
// if app crashes mid-write (prevents "Unexpected end of JSON input" on read)
await writeFileAtomic(partialPath, JSON.stringify(partialMessage, null, 2));
return Ok(undefined);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Expand Down
4 changes: 3 additions & 1 deletion src/node/utils/sessionFile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "fs/promises";
import * as path from "path";
import writeFileAtomic from "write-file-atomic";
import type { Result } from "@/common/types/result";
import { Ok, Err } from "@/common/types/result";
import type { Config } from "@/node/config";
Expand Down Expand Up @@ -55,7 +56,8 @@ export class SessionFileManager<T> {
const sessionDir = this.config.getSessionDir(workspaceId);
await fs.mkdir(sessionDir, { recursive: true });
const filePath = this.getFilePath(workspaceId);
await fs.writeFile(filePath, JSON.stringify(data, null, 2));
// Atomic write prevents corruption if app crashes mid-write
await writeFileAtomic(filePath, JSON.stringify(data, null, 2));
return Ok(undefined);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Expand Down