diff --git a/App/backend/src/adapters/outbound/agent-source/codex/session-discovery.ts b/App/backend/src/adapters/outbound/agent-source/codex/session-discovery.ts index 57d2b41df..5c2cb1a47 100644 --- a/App/backend/src/adapters/outbound/agent-source/codex/session-discovery.ts +++ b/App/backend/src/adapters/outbound/agent-source/codex/session-discovery.ts @@ -5,6 +5,8 @@ import { dirname, join } from "node:path"; import { readJsonlObjects } from "../jsonl-lines.js"; import { readDirectoryIfExists } from "../read-directory.js"; +const ROLLOUT_FILE_SUFFIX = ".jsonl"; + export interface CodexSessionFile { /** Session file path. */ sessionFilePath: string; @@ -56,7 +58,7 @@ async function listRolloutFiles( continue; } - if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) { + if (entry.isFile() && isPrimaryRolloutFile(entry.name)) { const fileStat = await stat(path); files.push({ path, mtimeMs: fileStat.mtimeMs }); } @@ -71,6 +73,12 @@ async function listRolloutFiles( .map((file) => file.path); } +function isPrimaryRolloutFile(name: string): boolean { + return name.startsWith("rollout-") && + name.endsWith(ROLLOUT_FILE_SUFFIX) && + name.indexOf(ROLLOUT_FILE_SUFFIX) === name.length - ROLLOUT_FILE_SUFFIX.length; +} + async function readFirstCwd(filePath: string): Promise { try { for await (const record of readJsonlObjects(filePath)) { diff --git a/App/backend/src/adapters/outbound/agent-source/codex/tests/adapter.test.ts b/App/backend/src/adapters/outbound/agent-source/codex/tests/adapter.test.ts index 0a358c51c..ce2464055 100644 --- a/App/backend/src/adapters/outbound/agent-source/codex/tests/adapter.test.ts +++ b/App/backend/src/adapters/outbound/agent-source/codex/tests/adapter.test.ts @@ -69,6 +69,24 @@ describe("codex source adapter", () => { ]); }); + it("ignores backup copies of rollout files", async () => { + const fixture = createFixture(); + const backupPath = `${fixture.rolloutPath}.bak-strip-input-image.jsonl`; + writeFileSync( + backupPath, + JSON.stringify({ + timestamp: "2026-05-29T11:00:00.000Z", + type: "session_meta", + payload: { cwd: join(fixture.workspacePath, "backup-copy") } + }), + "utf8" + ); + + await expect(discoverCodexSessions({ root: fixture.sessionsRoot })).resolves.toEqual([ + expect.objectContaining({ sessionFilePath: fixture.rolloutPath }) + ]); + }); + it("redacts large image tool outputs without failing the scan", async () => { const fixture = createLargeImageFixture(); const adapter = createCodexSourceAdapter({ sessionsRoot: fixture.sessionsRoot }); diff --git a/App/backend/src/services/agent-source-service.ts b/App/backend/src/services/agent-source-service.ts index dc3d6a7f1..c0168a12d 100644 --- a/App/backend/src/services/agent-source-service.ts +++ b/App/backend/src/services/agent-source-service.ts @@ -557,6 +557,7 @@ async function collectSourceMessages( messages: [], errors: [] }; + const conversationIds = new Set(); emitProgress(scanOptions, { sourceId, @@ -585,7 +586,8 @@ async function collectSourceMessages( })) { scanOptions.signal?.throwIfAborted(); collected.messages.push(message); - if (!collected.conversationIds.includes(message.conversationId)) { + if (!conversationIds.has(message.conversationId)) { + conversationIds.add(message.conversationId); collected.conversationIds.push(message.conversationId); } if (collected.messages.length % SCAN_MESSAGE_YIELD_INTERVAL === 0) {