Skip to content

Commit a7eced0

Browse files
address review: require exact cwd match for project-scoped session resume/list
1 parent ae964c0 commit a7eced0

2 files changed

Lines changed: 49 additions & 8 deletions

File tree

src/agents/sessions/session-manager.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,4 +2625,48 @@ describe("SessionManager cwd collision guard", () => {
26252625
expect(listA.map((s) => s.path)).toEqual([sessionFileA]);
26262626
expect(listB.map((s) => s.path)).toEqual([sessionFileB]);
26272627
});
2628+
2629+
it("ignores cwd-less sessions when resuming or listing for a specific cwd", async () => {
2630+
const agentDir = await makeTempDir();
2631+
const cwd = "/home/alice/dev/client/app";
2632+
const sessionDir = getDefaultSessionDir(cwd, agentDir);
2633+
2634+
const matchingFile = path.join(sessionDir, "matching.jsonl");
2635+
const cwdlessFile = path.join(sessionDir, "cwdless.jsonl");
2636+
2637+
await fs.writeFile(
2638+
matchingFile,
2639+
`${JSON.stringify(buildSessionHeader(cwd, "matching"))}\n`,
2640+
"utf8",
2641+
);
2642+
await fs.writeFile(
2643+
cwdlessFile,
2644+
`${JSON.stringify({
2645+
type: "session",
2646+
version: CURRENT_SESSION_VERSION,
2647+
id: "cwdless",
2648+
timestamp: "2026-06-18T00:00:00.000Z",
2649+
})}\n`,
2650+
"utf8",
2651+
);
2652+
2653+
// Make the cwd-less file strictly newer; without the cwd requirement it
2654+
// would be picked for the requested cwd.
2655+
await fs.utimes(
2656+
matchingFile,
2657+
new Date("2026-06-18T00:00:00.000Z"),
2658+
new Date("2026-06-18T00:00:00.000Z"),
2659+
);
2660+
await fs.utimes(
2661+
cwdlessFile,
2662+
new Date("2026-06-18T00:00:01.000Z"),
2663+
new Date("2026-06-18T00:00:01.000Z"),
2664+
);
2665+
2666+
expect(findMostRecentSession(sessionDir, { cwd })).toBe(matchingFile);
2667+
expect(SessionManager.continueRecent(cwd, sessionDir).getSessionFile()).toBe(matchingFile);
2668+
2669+
const list = await SessionManager.list(cwd, sessionDir);
2670+
expect(list.map((s) => s.path)).toEqual([matchingFile]);
2671+
});
26282672
});

src/agents/sessions/session-manager.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,13 +1199,10 @@ function sessionFileMatchesCwd(
11991199
if (!cwd) {
12001200
return true;
12011201
}
1202-
// Older sessions may not record a cwd; preserve backward compatibility by
1203-
// treating a missing/empty cwd as a match rather than orphaning them.
1204-
const headerCwd = header?.cwd;
1205-
if (!headerCwd) {
1206-
return true;
1207-
}
1208-
return headerCwd === cwd;
1202+
// Shipped session headers (e.g. v2026.5.28, v2026.6.10) always record cwd,
1203+
// so project-scoped resume/list requires an exact cwd match. cwd-less files
1204+
// are not silently adopted as belonging to an arbitrary project.
1205+
return header?.cwd === cwd;
12091206
}
12101207

12111208
/** Exported for testing */
@@ -3035,7 +3032,7 @@ export class SessionManager {
30353032
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
30363033
// When the cwd encoder maps multiple cwds to the same directory, only
30373034
// return sessions that actually belong to the requested cwd.
3038-
return sessions.filter((session) => !session.cwd || session.cwd === cwd);
3035+
return sessions.filter((session) => session.cwd === cwd);
30393036
}
30403037

30413038
/**

0 commit comments

Comments
 (0)