Log why a resumed Claude agent has no history (#5818)

When a resumed Claude agent's transcript was missing, history loading
returned without a log line, and when reading or parsing the transcript
threw, the error was swallowed. Either way the agent opened with an
empty timeline and daemon.log said nothing. A missing transcript is now
logged at info with the session id, cwd and the path that was checked,
and a read or parse failure is logged as a warning with the error.

Closes #5438

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Giordano <hello@gabrielgiordano.com>
This commit is contained in:
paseo-bot[bot]
2026-10-01 06:56:46 +02:00
committed by GitHub
co-authored by Gabriel Giordano
parent e939dd4c7a
commit d30e99c858
2 changed files with 104 additions and 3 deletions
@@ -0,0 +1,93 @@
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import pino from "pino";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { ClaudeAgentClient } from "./agent.js";
import { claudeProjectDirSync } from "./project-dir.js";
/**
* A resumed Claude agent whose transcript is missing or unreadable opens with an empty timeline.
* The daemon log at its default level has to say which, and where it looked.
*/
const SESSION_ID = "history-load-session";
interface LogRecord {
level: number;
msg: string;
sessionId?: string;
historyPath?: string;
err?: { message?: string };
}
describe("ClaudeAgentSession persisted history load", () => {
let tempRoot: string;
let cwd: string;
let configDir: string;
let records: LogRecord[];
async function resume(): Promise<void> {
const client = new ClaudeAgentClient({
logger: pino({ level: "info" }, { write: (line: string) => records.push(JSON.parse(line)) }),
queryFactory: vi.fn(() => {
throw new Error("history load must not start a query");
}),
resolveVersion: async () => "2.1.220",
});
const session = await client.resumeSession(
{ provider: "claude", sessionId: SESSION_ID },
{ cwd },
);
await session.close();
}
function transcriptPath(): string {
return path.join(claudeProjectDirSync(cwd, { configDir }), `${SESSION_ID}.jsonl`);
}
beforeEach(() => {
tempRoot = mkdtempSync(path.join(os.tmpdir(), "claude-history-load-"));
cwd = path.join(tempRoot, "repo");
configDir = path.join(tempRoot, "claude-config");
mkdirSync(cwd, { recursive: true });
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
records = [];
});
afterEach(() => {
vi.unstubAllEnvs();
rmSync(tempRoot, { recursive: true, force: true });
});
test("logs the resolved transcript path when the transcript does not exist", async () => {
await resume();
expect(records).toContainEqual(
expect.objectContaining({
level: pino.levels.values.info,
msg: "No Claude transcript to load history from",
sessionId: SESSION_ID,
historyPath: transcriptPath(),
}),
);
});
test("logs a warning with the error when the transcript cannot be read", async () => {
// A directory at the transcript path exists but cannot be read as a file.
mkdirSync(transcriptPath(), { recursive: true });
await resume();
expect(records).toContainEqual(
expect.objectContaining({
level: pino.levels.values.warn,
msg: "Failed to load Claude history from transcript",
sessionId: SESSION_ID,
historyPath: transcriptPath(),
err: expect.objectContaining({ message: expect.stringContaining("EISDIR") }),
}),
);
});
});
@@ -4881,10 +4881,15 @@ class ClaudeAgentSession implements AgentSession {
}
private loadPersistedHistory(sessionId: string): void {
let historyPath: string | null = null;
try {
this.taskState.reset();
const historyPath = this.resolveHistoryPath(sessionId);
historyPath = this.resolveHistoryPath(sessionId);
if (!historyPath || !fs.existsSync(historyPath)) {
this.logger.info(
{ sessionId, cwd: this.config.cwd, historyPath },
"No Claude transcript to load history from",
);
return;
}
const content = fs.readFileSync(historyPath, "utf8");
@@ -4893,8 +4898,11 @@ class ClaudeAgentSession implements AgentSession {
readClaudeSidechainHistory(historyPath),
);
this.ingestPersistedHistory(content, replay);
} catch {
// ignore history load failures
} catch (error) {
this.logger.warn(
{ err: error, sessionId, historyPath },
"Failed to load Claude history from transcript",
);
}
}