From 43eebd32453c9be2a4ee5c8e5b5172abab24c86f Mon Sep 17 00:00:00 2001 From: flexi767 Date: Mon, 2 Nov 2026 22:50:02 +0100 Subject: [PATCH] fix(server): skip Codex page context in imported titles Derive fallback titles from the request following the Codex app page context. Preserve transcript text and literal or incomplete markup. Validated with 68 scanner/importer tests and check:changed at concurrency 1. Implemented with GPT-6 in Codex. --- .../src/project/AgentSessionScanner.test.ts | 44 ++++++++++++++++++- .../server/src/project/AgentSessionScanner.ts | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/server/src/project/AgentSessionScanner.test.ts b/apps/server/src/project/AgentSessionScanner.test.ts index 688b974e7658d..8dbf948d6eccd 100644 --- a/apps/server/src/project/AgentSessionScanner.test.ts +++ b/apps/server/src/project/AgentSessionScanner.test.ts @@ -2382,11 +2382,16 @@ describe("parseAgentSessionTranscript", () => { ]); }); - it("titles a Codex event after the request that follows injected setup blocks", () => { + it.each([ + ['{"page_id":null}'], + ['{"page_id":"page-1"}'], + [""], + ])("titles a Codex event after the request that follows injected setup blocks: %s", (page) => { const prompt = [ "\nGitHub\n", "# AGENTS.md instructions for /tmp/project\n\n\nRules\n", '\nTab\n', + page, "Fix the import titles", ].join("\n"); const thread = AgentSessionScanner.parseAgentSessionTranscript({ @@ -2407,6 +2412,43 @@ describe("parseAgentSessionTranscript", () => { expect(thread?.messages.map((message) => message.text)).toEqual([prompt]); }); + it.each([ + { + prompts: [ + '{"page_id":null}', + "Import my sessions", + ], + title: "Import my sessions", + }, + { + prompts: ["Explain in this transcript"], + title: "Explain in this transcript", + }, + { + prompts: ["Keep this incomplete example"], + title: "Keep this incomplete example", + }, + ])("derives a Codex title without discarding user text: $title", ({ prompts, title }) => { + const thread = AgentSessionScanner.parseAgentSessionTranscript({ + contents: [ + encodeTranscriptRecord({ type: "session_meta", payload: { id: "codex-session" } }), + ...prompts.map((message) => + encodeTranscriptRecord({ + type: "event_msg", + payload: { type: "user_message", message }, + }), + ), + ].join("\n"), + source: "codex", + providerInstanceId: ProviderInstanceId.make("codex"), + fallbackSessionId: "fallback", + lastActiveAtMs: Date.parse("2026-08-25T08:00:00.000Z"), + }); + + expect(thread?.title).toBe(title); + expect(thread?.messages.map((message) => message.text)).toEqual(prompts); + }); + it("skips sessions without a visible user message", () => { const thread = AgentSessionScanner.parseAgentSessionTranscript({ contents: JSON.stringify({ diff --git a/apps/server/src/project/AgentSessionScanner.ts b/apps/server/src/project/AgentSessionScanner.ts index bbd3ba5a631e0..dabe6ce855829 100644 --- a/apps/server/src/project/AgentSessionScanner.ts +++ b/apps/server/src/project/AgentSessionScanner.ts @@ -305,7 +305,7 @@ function foldClaudeFields(fields: ClaudeFields, record: DecodedTranscriptRecord) // Codex records setup context as user text ahead of the real request. Skip // these leading blocks when deriving a title; imported messages keep them. const CODEX_TITLE_PREAMBLE = - /^(?:\s*(?:<(recommended_plugins|environment_context|user_instructions|INSTRUCTIONS|in-app-browser-context)(?:\s[^>]*)?>[\s\S]*?<\/\1>|# AGENTS\.md instructions\b[^\n]*|## My request for Codex:))+/; + /^(?:\s*(?:<(recommended_plugins|environment_context|user_instructions|INSTRUCTIONS|in-app-browser-context|external_codex_apps_open_page)(?:\s[^>]*)?>[\s\S]*?<\/\1>|# AGENTS\.md instructions\b[^\n]*|## My request for Codex:))+/; // Codex appends an entry per rename; like Codex, the newest nonempty name wins. function parseCodexThreadNames(contents: string): ReadonlyMap {