You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(server): import Codex chats moved into a project from the scratch folder - #32880
A Codex chat started with No project lives in Codex's scratch folder under ~/Documents/Codex. If the user later moves it into a project in Codex, T3's session import still skips it. Moving a chat in Codex (thread/metadata/update { projectId }) leaves every cwd in the rollout pointing at the scratch folder, which the scanner excludes. Codex records the assignment only in its state database (<CODEX_HOME>/state_5.sqlite: threads.project_id, project_roots).
Reproduced with the real Codex app-server (codex-cli 0.163.0) in an isolated Codex home. A chat started in <home>/Documents/Codex/2026-11-08/new-chat was moved into a project rooted at <home>/code/my-project, and a control chat was started directly in that project. Codex's own thread/list { projectId } returns both chats. On main, T3's scanner offers the project with 1 chat, and recentThreads imports only the control chat.
Change
AgentSessionScanner reads Codex's project assignments read-only, with one query per Codex home: thread ID → project roots. It opens state_5.sqlite with node:sqlite{ readOnly: true }, the same way the server already reads other tools' databases. A missing, older or locked database falls back to the current behaviour.
When a Codex chat's rollout cwd is a path the scanner already excludes (such as the Codex scratch folder) and Codex assigns the chat to a project, the chat uses that project's first root as its cwd. This applies both when grouping scan candidates and when recentThreads checks the chat against the selected project. Chats in any other real directory keep their own cwd, so linked worktrees and other repositories are unaffected.
CODEX_ROLLOUT_ID_PATTERN now captures the thread ID and, for rewound rollouts, the rollout ID separately. The state database is keyed by thread ID, and the existing rewound-history lookup still uses the rollout ID.
vp test run apps/server/src/project/AgentSessionScanner.test.ts: 68 passed at head.
New test: "imports Codex chats moved from a scratch folder into a Codex project" fails on main (expected [ 'Started in the project' ] to deeply equal [ 'Started in the project', 'Started with no project' ]) and passes with the fix.
The same test also covers a rewound rollout (<thread>_<rollout>.jsonl) of a moved chat, and a chat assigned to the project from another real folder, which keeps its own cwd.
New test: "keeps Codex rollout cwds when the Codex state database is unreadable".
CI green on the head.
Runtime transcript: the real scanner, before and after, run against the Codex home that the real Codex app-server wrote:
== before (09a54f01bc)
SCAN candidates: [{"path":"<fakehome>/code/my-project","threadCount":1}]
RECENT THREADS for <fakehome>/code/my-project [{"tag":"Importable","id":"01a1ba2e-13b8-…","title":"Reply with the single word: hi"}]
== after (60005e5afd)
SCAN candidates: [{"path":"<fakehome>/code/my-project","threadCount":2}]
RECENT THREADS for <fakehome>/code/my-project [{"tag":"Importable","id":"01a1ba2e-13b8-…","title":"Reply with the single word: hi"},{"tag":"Importable","id":"01a1ba2e-0a2e-…","title":"Reply with the single word: hello"}]
What Codex wrote (rollout cwds and state database), synthetic data
Codex app-server (codex-cli 0.163.0), isolated CODEX_HOME=<fakehome>/.codex, driver: codex-driver.mjs
turn 01a1ba2e-0a2e-74f2-9b6e-687336ad6045 completed
project 01a1ba2e-13a5-7242-bfe1-ebcc2e4218db [{"path":"<fakehome>/code/my-project"}]
moved thread after update: cwd= <fakehome>/Documents/Codex/2026-11-08/new-chat projectId= 01a1ba2e-13a5-7242-bfe1-ebcc2e4218db path= <fakehome>/.codex/sessions/2026/11/08/rollout-2026-11-08T08-02-21-01a1ba2e-0a2e-74f2-9b6e-687336ad6045.jsonl
turn 01a1ba2e-13b8-7f10-ba05-18b6d8c520ff completed
threads in project: [{"id":"01a1ba2e-13b8-7f10-ba05-18b6d8c520ff","cwd":"<fakehome>/code/my-project","projectId":"01a1ba2e-13a5-7242-bfe1-ebcc2e4218db"},{"id":"01a1ba2e-0a2e-74f2-9b6e-687336ad6045","cwd":"<fakehome>/Documents/Codex/2026-11-08/new-chat","projectId":"01a1ba2e-13a5-7242-bfe1-ebcc2e4218db"}]
Rollout records carrying a cwd:
rollout-2026-11-08T08-02-21-01a1ba2e-0a2e-74f2-9b6e-687336ad6045.jsonl
session_meta cwd=<fakehome>/Documents/Codex/2026-11-08/new-chat
turn_context cwd=<fakehome>/Documents/Codex/2026-11-08/new-chat
rollout-2026-11-08T08-02-24-01a1ba2e-13b8-7f10-ba05-18b6d8c520ff.jsonl
session_meta cwd=<fakehome>/code/my-project
turn_context cwd=<fakehome>/code/my-project
state_5.sqlite threads(id, cwd, project_id) and project_roots:
01a1ba2e-0a2e-74f2-9b6e-687336ad6045|<fakehome>/Documents/Codex/2026-11-08/new-chat|01a1ba2e-13a5-7242-bfe1-ebcc2e4218db
01a1ba2e-13b8-7f10-ba05-18b6d8c520ff|<fakehome>/code/my-project|01a1ba2e-13a5-7242-bfe1-ebcc2e4218db
01a1ba2e-13a5-7242-bfe1-ebcc2e4218db|0|<fakehome>/code/my-project
Codex app-server driver used for the reproduction (scratch, not committed)
import{spawn}from"node:child_process";importreadlinefrom"node:readline";constHOME=process.argv[2];constscratch=`${HOME}/Documents/Codex/2026-11-08/new-chat`;constproject=`${HOME}/code/my-project`;constchild=spawn("codex",["app-server"],{env: { ...process.env,HOME,CODEX_HOME: `${HOME}/.codex`},stdio: ["pipe","pipe","inherit"]});constrl=readline.createInterface({input: child.stdout});letid=0;constpending=newMap();constwaiters=[];rl.on("line",(l)=>{constm=JSON.parse(l);if(m.id!==undefined&&pending.has(m.id)){pending.get(m.id)(m);pending.delete(m.id);return;}if(m.method){for(constwof[...waiters])if(w.pred(m)){waiters.splice(waiters.indexOf(w),1);w.res(m);}}});constcall=(method,params)=>newPromise((res)=>{consti=++id;pending.set(i,(m)=>{if(m.error)console.log("ERR",method,JSON.stringify(m.error));res(m.result);});child.stdin.write(JSON.stringify({id: i, method, params })+"\n");});constwaitFor=(pred)=>newPromise((res)=>waiters.push({ pred, res }));awaitcall("initialize",{clientInfo: {name: "repro",version: "0"},capabilities: {experimentalApi: true}});child.stdin.write(JSON.stringify({method: "initialized"})+"\n");asyncfunctionchat(cwd,text,projectId){const{ thread }=awaitcall("thread/start",{ cwd, ...(projectId ? { projectId } : {})});constdone=waitFor((m)=>m.method==="turn/completed"&&m.params.threadId===thread.id);awaitcall("turn/start",{threadId: thread.id,input: [{type: "text", text }]});constc=awaitdone;console.log("turn",thread.id,c.params.turn.status);returnthread.id;}constmoved=awaitchat(scratch,"Reply with the single word: hello",null);const{project: p}=awaitcall("project/create",{idempotencyKey: "k1",name: "my-project",roots: [{path: project}]});console.log("project",p.id,JSON.stringify(p.roots));constupd=awaitcall("thread/metadata/update",{threadId: moved,projectId: p.id});console.log("moved thread after update: cwd=",upd.thread.cwd,"projectId=",upd.thread.projectId,"path=",upd.thread.path);constcontrol=awaitchat(project,"Reply with the single word: hi",p.id);constlist=awaitcall("thread/list",{projectId: p.id});console.log("threads in project:",JSON.stringify(list.data.map((t)=>({id: t.id,cwd: t.cwd,projectId: t.projectId}))));console.log(JSON.stringify({ moved, control }));child.kill();
Evidence
Server-only change, so the captures are before/after transcripts of the real AgentSessionScanner (scan candidates and recentThreads results), with a warm scan→recentThreads sequence and a cold recentThreads on a fresh scanner in every fixture. Viewport and theme do not apply. Environment: Node v26.12.1, Linux x86_64, synthetic Codex homes using the state_5.sqlite schema the real codex-cli 0.163.0 app-server creates (schema receipt, schema dump, probe). 30 scanner invocations, all with matching fixture hashes on both sides (receipts). Fixture matrix, index.
What else changed: nothing outside the mapped-chat rows. In the composite fixture the project's count rises from 1 to 5 (the moved chat plus three rewind files), which also moves its last activity forward.
Unverified: cannot-verify macos (Codex Desktop end to end; the move was driven through the same app-server request Codex uses). A Codex sqlite_home setting that relocates the state database keeps the current behaviour.
AI-assisted; verified with the tests and tools listed above.
…ot imported
Codex Desktop starts "No project" chats in ~/Documents/Codex/<date>/<slug>
and records a later move into a project only in state_5.sqlite. The
rollout keeps the scratch cwd, so the session scanner never offers the
moved chat under the project. This test reproduces #32873 and fails on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…older
Codex Desktop starts "No project" chats in ~/Documents/Codex/<date>/<slug>
and records a later move into a project only in its state DB
(state_5.sqlite). The rollout keeps the scratch cwd, which T3 excludes, so
the session scanner never offered the moved chat under its project.
Codex discovery now reads the thread-to-project roots from that DB
read-only, once per Codex home. A chat whose rollout cwd lies outside its
Codex project's roots is grouped and imported under the project's first
root; chats already inside the project keep their own cwd. A missing,
locked or unreadable DB leaves the old behaviour unchanged.
Fixes#32873
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chats assigned to a Codex project but running in another real directory,
such as a linked worktree, keep their own cwd.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macroscope's review found this PR approvable — This enables discovery and import of Codex chats moved from scratch folders into projects using local project metadata. Database access is read-only, failures preserve existing behavior, and destination checks remain in place.
No code changes detected at 6d24bd7. Prior analysis still applies.
You can add or adjust custom eligibility rules. Learn more.
Reviewing files that changed from the base of the PR and between df3ffc1 and 6d24bd7.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
Trigger review
No actionable comments were generated in the recent review.
Walkthrough
The scanner now reads Codex project-root mappings from state_5.sqlite and uses them when grouping rollout transcripts and matching recent threads to workspaces. It also recognizes updated rollout filename forms and supports inherited rollout lookup through an optional second UUID suffix.
Changes: Codex session scanning
Layer / File(s)
Summary
Read Codex project-root state apps/server/src/project/AgentSessionScanner.ts, apps/server/src/project/AgentSessionScanner.test.ts
The scanner reads thread-to-project-root mappings from state_5.sqlite, skips invalid roots, and returns an empty mapping if reading fails. The test covers unreadable database contents.
Codex rollout transcripts are associated with mapped project roots. Resolved working directories are used for workspace grouping and recent-thread matching. Rollout lookup supports an optional second UUID suffix. Tests cover project-associated chats with scratch-directory working paths.
pingdotgg/t3code#32122: Both changes affect inherited-history handling and rollout lookup for rewound Codex sessions.
Suggested labels:bugfix
Suggested reviewers:marcodotio, macroscopeapp
Merge Risk: Minimal · previous review 75e3f
The change lets Codex chats moved into a project be imported with that project, and it falls back to the existing behavior if the Codex state database is missing or unreadable. No merge-blocking risk was identified.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names the main change: Codex chats moved into a project from the scratch folder are now imported. It follows the conventional commit form and matches the code changes in AgentSessionScanner,…
Linked Issues check
Objective [#32873]: when a Codex chat started in the scratch folder is assigned to a project in Codex, T3 import must include it with the control chat. Implementation at head: (1) readCodexProjectRoot…
Out of Scope Changes check
All changed hunks in AgentSessionScanner.ts connect to the issue. The node:sqlite import and readCodexProjectRoots are the state-database source for the project mapping. CodexStateReadError and the fa…
Description check
The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the issue and expected behavior, links the related issue, and reports focused tests, r…
Full details: Title check
Explanation
The title names the main change: Codex chats moved into a project from the scratch folder are now imported. It follows the conventional commit form and matches the code changes in AgentSessionScanner, which resolve project roots for excluded scratch-folder cwds.
Full details: Linked Issues check
Explanation
Objective [#32873]: when a Codex chat started in the scratch folder is assigned to a project in Codex, T3 import must include it with the control chat. Implementation at head: (1) readCodexProjectRoots reads thread ID to project roots from <CODEX_HOME>/state_5.sqlite with DatabaseSync and readOnly: true. (2) projectCwd replaces a rollout cwd with the project's first root only when the rollout cwd matches isExcludedProjectPath. (3) projectCwd is applied in candidate grouping (discoverable cwd key) and in recentThreads matching, so the moved chat groups under the project and passes the selected-project check. Chats in other real directories keep their own cwd. A missing, older, or locked database returns an empty map, so the existing behavior remains. Evidence limits: the diff confirms this logic. The test results and the 1-to-2 runtime transcript are author-reported; I did not run the tests. The macOS Codex Desktop flow is not verified end to end. The issue states those desktop steps are not required coding work. The objective is met by the code at head.
Full details: Out of Scope Changes check
Explanation
All changed hunks in AgentSessionScanner.ts connect to the issue. The node:sqlite import and readCodexProjectRoots are the state-database source for the project mapping. CodexStateReadError and the fallback provide the required read-only, unreadable-database behavior. MAX_CODEX_PROJECT_ROOT_ROWS bounds that read. projectCwd and its uses in grouping and recentThreads implement the moved-chat behavior. CODEX_ROLLOUT_ID_PATTERN now captures thread and rollout IDs separately. The rewound-history lookup uses match[2] ?? match[1], which keeps the rollout ID for that lookup and the thread ID for the state database. The discovery and candidate types carry projectRoots only to support these paths. No unrelated behavior change appears in the diff. The test file changes are supporting coverage for the same behavior, as stated in the PR description.
Full details: Description check
Explanation
The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the issue and expected behavior, links the related issue, and reports focused tests, runtime evidence, and unverified cases. The author notes AI assistance but does not name the model and harness, as the template requests.
Finishing Touches
Generate docstrings
Commit to this branch
Create a new PR
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
Environment for both sides: real web app and server in separate worktrees; Linux Chromium, 1280×800, light theme, fresh T3 databases, identical synthetic Codex history.
Steps: seed one chat with an original scratch-folder cwd and a current project assignment, plus one chat started directly in that project. Open the welcome wizard → Review all 1 → Other folders → Select all → Import 1 project and start. Expand Settled and inspect imported chats.
Private operational details are masked. Final stills passed the 500 ms identical-capture check. Codex Desktop's macOS move interaction was represented by synthetic on-disk history; it was not executed here.
AI-assisted; verified with the tests and tools listed above.
Not shown:
Creating and moving a chat in Codex Desktop on macOS was not executed: this Linux VM used synthetic rollouts and state_5.sqlite project assignments.
Rewound rollout handling, multiple project roots, and missing or unreadable Codex database fallback were not exercised by this two-chat reproduction.
Authenticated Codex continuation was not exercised. The installed CLI rejected the minimal synthetic database during its provider probe; the real welcome import still completed on both sides.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bugfixFix PR opened by an issue lead; enters the PR looppr:needs-verificationReviewed and green; needs a run on a desktop, Android or iOS device and captures postedsize:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Codex chat started with No project lives in Codex's scratch folder under
~/Documents/Codex. If the user later moves it into a project in Codex, T3's session import still skips it. Moving a chat in Codex (thread/metadata/update { projectId }) leaves everycwdin the rollout pointing at the scratch folder, which the scanner excludes. Codex records the assignment only in its state database (<CODEX_HOME>/state_5.sqlite:threads.project_id,project_roots).Reproduced with the real Codex app-server (codex-cli 0.163.0) in an isolated Codex home. A chat started in
<home>/Documents/Codex/2026-11-08/new-chatwas moved into a project rooted at<home>/code/my-project, and a control chat was started directly in that project. Codex's ownthread/list { projectId }returns both chats. On main, T3's scanner offers the project with 1 chat, andrecentThreadsimports only the control chat.Change
AgentSessionScannerreads Codex's project assignments read-only, with one query per Codex home: thread ID → project roots. It opensstate_5.sqlitewithnode:sqlite{ readOnly: true }, the same way the server already reads other tools' databases. A missing, older or locked database falls back to the current behaviour.cwdis a path the scanner already excludes (such as the Codex scratch folder) and Codex assigns the chat to a project, the chat uses that project's first root as its cwd. This applies both when grouping scan candidates and whenrecentThreadschecks the chat against the selected project. Chats in any other real directory keep their own cwd, so linked worktrees and other repositories are unaffected.CODEX_ROLLOUT_ID_PATTERNnow captures the thread ID and, for rewound rollouts, the rollout ID separately. The state database is keyed by thread ID, and the existing rewound-history lookup still uses the rollout ID.Scope and approval
Fixes #32873
Verification
Head df3ffc1249 (main merged in). Before = merge-base fa2f4ff2a2.
vp test run apps/server/src/project/AgentSessionScanner.test.ts: 68 passed at head.expected [ 'Started in the project' ] to deeply equal [ 'Started in the project', 'Started with no project' ]) and passes with the fix.<thread>_<rollout>.jsonl) of a moved chat, and a chat assigned to the project from another real folder, which keeps its own cwd.What Codex wrote (rollout cwds and state database), synthetic data
Codex app-server driver used for the reproduction (scratch, not committed)
Evidence
Server-only change, so the captures are before/after transcripts of the real
AgentSessionScanner(scan candidates andrecentThreadsresults), with a warm scan→recentThreadssequence and a coldrecentThreadson a fresh scanner in every fixture. Viewport and theme do not apply. Environment: Node v26.12.1, Linux x86_64, synthetic Codex homes using thestate_5.sqliteschema the real codex-cli 0.163.0 app-server creates (schema receipt, schema dump, probe). 30 scanner invocations, all with matching fixture hashes on both sides (receipts). Fixture matrix, index.<thread>_<rollout>.jsonl)positiononlyWhat else changed: nothing outside the mapped-chat rows. In the composite fixture the project's count rises from 1 to 5 (the moved chat plus three rewind files), which also moves its last activity forward.
Proof audit: sufficient. The auditor rebuilt R05 and R07 from the uploaded scripts at the head with zero behavioural delta. Scripts: scanner-driver.mjs, seed-fixtures.py, run-captures.py, package-evidence.py.
Verification log
AgentSessionScanner.test.ts; the new regression test fails on main.cannot-verify macos(Codex Desktop end to end; the move was driven through the same app-server request Codex uses). A Codexsqlite_homesetting that relocates the state database keeps the current behaviour.AI-assisted; verified with the tests and tools listed above.