fix(server): propagate Claude transcript replay read errors

This commit is contained in:
Mohamed Boudra
2026-10-06 13:32:20 +02:00
parent 5991d2f789
commit 589ff78db0
2 changed files with 25 additions and 15 deletions
@@ -1,4 +1,4 @@
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import pino from "pino";
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { ClaudeAgentClient } from "./agent.js";
import { claudeProjectDirSync } from "./project-dir.js";
import { readClaudeReplayHistory } from "./transcript-history.js";
/**
* A resumed Claude agent whose transcript is missing or unreadable opens with an empty timeline.
@@ -90,4 +91,14 @@ describe("ClaudeAgentSession persisted history load", () => {
}),
);
});
test("propagates read errors when a transcript disappears before replay", () => {
const file = transcriptPath();
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, "");
const history = readClaudeReplayHistory(file);
rmSync(file);
expect(() => [...history.parentEntries]).toThrow("ENOENT");
});
});
@@ -26,14 +26,18 @@ interface RecordLocation {
function* locatedRecords(
file: string,
): Generator<{ entry: TranscriptEntry; location: RecordLocation }> {
let fd: number;
const fd = fs.openSync(file, "r");
try {
fd = fs.openSync(file, "r");
} catch {
return;
}
try {
let remaining = fs.fstatSync(fd).size;
const stat = fs.fstatSync(fd);
// Opening a directory succeeds on some platforms; it must never look like empty history.
if (stat.isDirectory()) {
throw Object.assign(new Error(`EISDIR: illegal operation on a directory, read '${file}'`), {
code: "EISDIR",
syscall: "read",
path: file,
});
}
let remaining = stat.size;
let offset = 0;
let pieces: Buffer[] = [];
let length = 0;
@@ -110,17 +114,12 @@ function workflowRecords(files: readonly string[]): Iterable<TranscriptEntry> {
let fd: number | null = null;
try {
for (const location of locations) {
if (currentFile !== location.file) {
if (fd === null || currentFile !== location.file) {
if (fd !== null) fs.closeSync(fd);
fd = null;
currentFile = location.file;
try {
fd = fs.openSync(currentFile, "r");
} catch {
continue;
}
fd = fs.openSync(currentFile, "r");
}
if (fd === null) continue;
const buffer = Buffer.allocUnsafe(location.length);
let read = 0;
while (read < buffer.length) {