mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-28 20:22:45 +00:00
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:
co-authored by
Gabriel Giordano
parent
e939dd4c7a
commit
d30e99c858
@@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user