Fix recent session resume with long headers (#94578)

Merged via squash.

Prepared head SHA: 8102961184
Co-authored-by: rohitjavvadi <76606932+rohitjavvadi@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
This commit is contained in:
Rohit
2026-06-23 13:51:15 +05:30
committed by GitHub
parent dd76fdceb6
commit 695cea68f5
2 changed files with 98 additions and 5 deletions
@@ -11,6 +11,7 @@ import { prepareSessionManagerForRun } from "../embedded-agent-runner/session-ma
import { repairSessionFileIfNeeded } from "../session-file-repair.js";
import {
CURRENT_SESSION_VERSION,
findMostRecentSession,
loadEntriesFromFile,
SessionManager,
type SessionEntry,
@@ -122,6 +123,72 @@ describe("SessionManager.open", () => {
expect(entries.filter((entry) => entry.type === "session")).toHaveLength(1);
});
it("continues a valid recent session when the header exceeds the first read chunk", async () => {
const dir = await makeTempDir();
const sessionFile = path.join(dir, "long-header-session.jsonl");
const longCwd = `/tmp/${"deep/".repeat(120)}`;
const header = {
type: "session",
version: CURRENT_SESSION_VERSION,
id: "long-header-session",
timestamp: "2026-06-18T00:00:00.000Z",
cwd: longCwd,
};
const userEntry = {
type: "message",
id: "user-1",
parentId: null,
timestamp: "2026-06-18T00:00:01.000Z",
message: { role: "user", content: "resume me" },
};
await fs.writeFile(
sessionFile,
`${JSON.stringify(header)}\n${JSON.stringify(userEntry)}\n`,
"utf8",
);
expect(Buffer.byteLength(JSON.stringify(header), "utf8")).toBeGreaterThan(512);
expect(loadEntriesFromFile(sessionFile)).toHaveLength(2);
expect(findMostRecentSession(dir)).toBe(sessionFile);
expect(SessionManager.continueRecent(longCwd, dir).getSessionFile()).toBe(sessionFile);
});
it("skips oversized recent session headers instead of hiding valid sessions", async () => {
const dir = await makeTempDir();
const validSessionFile = path.join(dir, "valid-session.jsonl");
const oversizedSessionFile = path.join(dir, "oversized-header-session.jsonl");
const validHeader = {
type: "session",
version: CURRENT_SESSION_VERSION,
id: "valid-session",
timestamp: "2026-06-18T00:00:00.000Z",
cwd: "/tmp/task-repo",
};
const oversizedHeader = {
type: "session",
version: CURRENT_SESSION_VERSION,
id: "oversized-header-session",
timestamp: "2026-06-18T00:00:01.000Z",
cwd: `/tmp/${"deep/".repeat(14_000)}`,
};
await fs.writeFile(validSessionFile, `${JSON.stringify(validHeader)}\n`, "utf8");
await fs.writeFile(oversizedSessionFile, `${JSON.stringify(oversizedHeader)}\n`, "utf8");
await fs.utimes(
validSessionFile,
new Date("2026-06-18T00:00:00.000Z"),
new Date("2026-06-18T00:00:00.000Z"),
);
await fs.utimes(
oversizedSessionFile,
new Date("2026-06-18T00:00:01.000Z"),
new Date("2026-06-18T00:00:01.000Z"),
);
expect(Buffer.byteLength(JSON.stringify(oversizedHeader), "utf8")).toBeGreaterThan(64 * 1024);
expect(findMostRecentSession(dir)).toBe(validSessionFile);
});
it("still migrates old transcript versions while bypassing the warm cache", async () => {
const dir = await makeTempDir();
const sessionFile = path.join(dir, "session.jsonl");
+31 -5
View File
@@ -50,6 +50,9 @@ import type { BashExecutionMessage, CustomMessage } from "./messages.js";
export { CURRENT_SESSION_VERSION };
const SESSION_HEADER_READ_CHUNK_BYTES = 4096;
const MAX_SESSION_HEADER_BYTES = 64 * 1024;
export interface SessionHeader {
type: "session";
version?: number; // v1 sessions don't have this
@@ -1141,13 +1144,36 @@ function recoverCorruptSessionEntries(filePath: string, cwd: string): FileEntry[
return [header, ...recoveredEntries];
}
function readFirstSessionFileLine(filePath: string): string | undefined {
const fd = openSync(filePath, "r");
try {
const chunks: Buffer[] = [];
let totalBytes = 0;
while (totalBytes < MAX_SESSION_HEADER_BYTES) {
const buffer = Buffer.alloc(
Math.min(SESSION_HEADER_READ_CHUNK_BYTES, MAX_SESSION_HEADER_BYTES - totalBytes),
);
const bytesRead = readSync(fd, buffer, 0, buffer.length, totalBytes);
if (bytesRead === 0) {
break;
}
const newlineIndex = buffer.indexOf(0x0a);
if (newlineIndex >= 0 && newlineIndex < bytesRead) {
chunks.push(buffer.subarray(0, newlineIndex));
return Buffer.concat(chunks).toString("utf8");
}
chunks.push(buffer.subarray(0, bytesRead));
totalBytes += bytesRead;
}
return chunks.length > 0 ? Buffer.concat(chunks).toString("utf8") : undefined;
} finally {
closeSync(fd);
}
}
function isValidSessionFile(filePath: string): boolean {
try {
const fd = openSync(filePath, "r");
const buffer = Buffer.alloc(512);
const bytesRead = readSync(fd, buffer, 0, 512, 0);
closeSync(fd);
const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
const firstLine = readFirstSessionFileLine(filePath);
if (!firstLine) {
return false;
}