# Proof audit, pass 1

[PR #32880](https://git.cafe/pingdotgg/t3code/pull/32880) ([GitHub #32880](https://github.com/pingdotgg/t3code/pull/32880)), addressing [#32873](https://github.com/pingdotgg/t3code/issues/32873).

Reviewed head [df3ffc1249](https://github.com/pingdotgg/t3code/commit/df3ffc1249ca06ef43aaebe6a534f96034354700) against pinned main/merge-base [fa2f4ff2a2](https://github.com/pingdotgg/t3code/commit/fa2f4ff2a2bac5ac92e07749f6b83b0003d642a0). Read-only source and forge inspection; no tests rerun, no runtime capture produced, no server or provider started, and no worktree edits or PR comments. All fixture data below is synthetic.

## Findings first

1. **The core before/after proof already exists and remains applicable.** The PR-body transcript shows a real Codex app-server creating a scratch chat, assigning it through `thread/metadata/update`, and creating a control chat directly in the project. It then shows the real scanner changing the project count from 1 to 2 and `recentThreads` returning both chats as `Importable`. The scanner blob at the transcript's after revision [60005e5afd](https://github.com/pingdotgg/t3code/commit/60005e5afd) is identical to the reviewed head (blob `1b32de22a8878fdf07db29403cd9cedb9692ea7d`); the before scanner blob is identical to the pinned base (`dc67bd9387e04b91fa61d34ea47929c056256796`). A rebase alone does not invalidate this evidence.
2. **The requested complete changed-path runtime proof is still missing R03–R07.** The existing transcript does not show an unmapped scratch chat remaining excluded, a mapped chat in another real directory retaining that directory, a mapped rewound rollout using its thread ID while history lookup uses its rollout ID, database-read failure preserving both scanner outputs, or ordered multi-root selection. These can be covered with three small additional scanner transcripts, without a browser. This is a proof gap, not a confirmed implementation defect.
3. **Tests are useful evidence, but are not runtime captures of these rows.** The added moved-chat test asserts both outputs for R02, R04 and the filename/mapping portion of R05. Its rewound fixture contains no `history_base`; the pre-existing rewind test separately covers one inherited original rollout. The new corrupt-database test asserts `scan` only, not `recentThreads`. The PR reports 68 passing tests and `check:changed`; this audit inspected assertions but did not rerun them. No test found for ordered multiple project roots. No new capture is required for unchanged UI layouts, themes, widths, or a native Codex Desktop walkthrough.

## Observable paths and scope

- [Scanner service and outcomes](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/project/AgentSessionScanner.ts#L200): `scan` returns candidate path/title, provider sources, thread count, latest activity, git identity, persisted-project identity/flag, scan timestamp and optional discovery truncation. `recentThreads` streams `Importable`, `AlreadyImported`, `Duplicate`, or `Skipped`; filtered/ineligible transcripts can produce no event at all.
- [Database and cwd resolution](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/project/AgentSessionScanner.ts#L1252): per resolved Codex home, read `state_5.sqlite` read-only; join thread ID to roots ordered by thread rowid and root position; reject non-string IDs/roots and blank roots; cap at 100,000 joined rows; on any database failure use an empty map. Canonical filenames use their single UUID for mapping; rewound filenames use the first UUID for mapping and the second for inherited-rollout lookup. Only an excluded rollout cwd is replaced, using the first retained root.
- [Grouping and scan validation](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/project/AgentSessionScanner.ts#L1420): missing cwd/subagent stops before mapping; group by provider instance plus resolved cwd; merge by directory identity; retain existing absolute-path, directory, exclusion, realpath and linked-worktree checks. Mapping can introduce a project from an otherwise empty scan, increase an existing count, change activity/order/title/git metadata through the selected directory, or keep the scan empty.
- [Recent-thread preparation and parsing](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/project/AgentSessionScanner.ts#L1724): consumes cached candidates after a scan or discovers them on a cold call; checks selected-root identity, 30-day mtime window, budgets, file identity and prior imports; then applies mapping again to the parsed cwd before validating identity and reconstructing history. Output messages, session identity and de-duplication remain relevant for a rewound chat.
- Production callers: [WebSocket scan/import handlers](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/ws.ts#L3058) call the scanner and [importer](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/server/src/project/AgentSessionImporter.ts#L234), respectively. The importer is the production `recentThreads` caller. [Welcome onboarding](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/web/src/components/onboarding/WelcomePage.tsx#L1360) obtains scan data through [useProjectScans](https://github.com/pingdotgg/t3code/blob/df3ffc1249ca06ef43aaebe6a534f96034354700/apps/web/src/onboarding/useProjectScans.ts#L13), creates/selects the project, and invokes import. Web and Electron use this UI; no direct mobile or agent-tool caller of these RPCs was found. Importer RPC may construct a separate scanner from the scan RPC, so exercise both a warm scan-then-recent instance and a cold recent instance in the capture driver.
- No changed React component, prop, CSS, loading indicator, error presentation, theme or viewport branch. The welcome page can show no projects, a populated list, changed counts and import results because the service data changes; its existing loading/error/truncation/selection/retry flows are not separately changed. Database-read failure is swallowed, not a new visible error. Local/remote/relay all use the destination server filesystem through the same RPC. Provider work is Codex-only; Claude/Pi see undefined mapping; other providers are outside this scanner. Platform affects existing path/exclusion matching, not a new platform branch. macOS Desktop, Windows casing and relocated `sqlite_home` are not claimed verified.

## Fixture matrix

Notation: H is a synthetic home; S = H/Documents/Codex/2026-11-08/new-chat; P = H/code/project; Q = H/code/other. P and Q exist and are not linked worktrees unless stated. Each ordinary fixture has a unique UUID, one visible user message and recent mtime. Always print the full candidate `{path, threadCount}` list and all recent outcome tags/IDs/titles for P and Q, including explicit empty arrays. For R05 print messages as well. An absent result is different from a `Skipped` result.

`Required` means part of the minimum added capture set for the changed observable behavior. `Supplemental` means inherited behavior or a boundary/defensive variant, listed for completeness, not an additional runtime gate for this narrow fix. Existing assertion coverage is source-inspected, not independently executed.

| Row / fixture | Data and observable result | Diff effect / capture requirement | Seed path, capture type | Existing PR-body runtime evidence / status |
|---|---|---|---|---|
| R01 — Direct project control | Original `<thread>.jsonl`, cwd P, DB assignment P. Scan P:1; recent(P) imports control. Combined with R02: P:2. | Preserved bypass branch; required baseline, already captured (M0). | Real app-server `thread/start` cwd P and visible turn; static scanner transcript. | **Covered.** Same control appears before and after. Also present in new moved-chat test. |
| R02 — Scratch chat assigned to P | Original rollout cwd S remains unchanged; state DB joins thread ID to P. Alone scan empty→P:1; with R01 P:1→P:2. recent(P) gains the moved chat. | Primary behavior change; required, already captured (M0). | Real app-server create scratch chat, create project with root P, `thread/metadata/update {projectId}`. **Recording:** request/result plus before/after scanner transcript; existing evidence qualifies at service boundary. | **Covered** for moved+control combined. Existing body shows assignment, rollout cwds, DB rows and both outputs. Empty→one is the same mapping/grouping path, so no extra required run. |
| R03 — Excluded and unassigned | Recent original rollout cwd S, no joined root (null project/no root). It must contribute neither a candidate nor any recent(P) outcome; R01 survives. | Undefined-mapping branch must retain exclusions; **required M1** negative control. Equivalent supplemental variants: empty/missing join, invalid filename UUID extraction, rejected blank/non-string DB row, or thread beyond row cap. | Normal scratch chat with no metadata assignment through app-server. Malformed DB/filename and >100,000-row variants require **labelled harness**; static transcript, no live interaction needed. | **Missing runtime.** Before R02 is not this row: it already has an assignment, and the old scanner ignores all DB mappings. Existing general exclusion/malformed tests are partial support, not this mapped-vs-unmapped control. |
| R04 — Another real cwd wins | Rollout cwd Q, assigned to P. Scan has Q:1; recent(P) excludes it and recent(Q) imports it. Linked ordinary worktree variant retains its original cwd too; scan may omit linked worktrees under existing policy. | New conditional must not redirect ordinary directories; **required M1**. | Real app-server start in Q and assign to project P. Static transcript after setup; to demonstrate the reassignment action, **recording** request/result sequence. | **Test only; runtime missing.** New moved-chat test checks Q scan count and absence from recent(P), but not recent(Q). |
| R05 — Rewound assigned scratch chat | File `<thread>_<rollout>.jsonl`, DB key is thread UUID, cwd S, root P. Scan counts the discovered files under P; recent(P) imports logical thread once. A history chain referencing a rewound rollout must retain inherited messages and exclude abandoned messages. | Both changed regex consumers and mapping transport; **required M1**. | Use real app-server rewind if supported; otherwise **labelled harness** with original + first rewind + second rewind whose `history_base.thread_id` is the first rewind's rollout UUID and valid byte offsets. **Recording:** ordered rewind/history/import transcript, showing IDs and final messages. | **Test only; runtime missing.** New test covers two-UUID mapping without inherited history; existing one-rewind test covers inheritance from an original rollout. Neither body capture nor those tests shows inheritance from a suffixed parent rollout. |
| R06 — Database unavailable, control survives | Same R01/R02 files; DB cannot be read. Scan P:1; recent(P) imports only R01, no scan failure. Missing file, old schema, corrupt file and exclusive lock converge on empty-map fallback. | New failure path; **required M2**, one deterministic failure representative. | **Labelled harness:** cloned synthetic Codex home with corrupt DB; do not change live Codex data. Static before/after transcript of both methods. Lock variant is **recording**, hold/release tracked connection; not a latency benchmark. | **Partial test only; runtime missing.** Added corrupt-DB test asserts scan(P:1) only. Body's prose promises fallback but contains no failure transcript. No need to capture every equivalent DB error. |
| R07 — Ordered multiple roots | Assigned scratch thread has root rows inserted out of order: Q at position 1, P at position 0. Scan uses P only; recent(P) imports, recent(Q) does not. No double count or expansion to every root. | New ordered-root read / first-root selection; **required M3**. | App-server project with multiple roots if supported; otherwise **labelled harness** writing only synthetic SQLite rows. Static scanner transcript. Can be a second M1 phase if it uses a fresh scanner. | **Uncovered** by existing runtime transcript or inspected focused tests (both have one root). |
| R08 — Assigned destination rejected | First mapped root relative or excluded: scan omits it; relative-root candidate is not eligible, excluded requested root yields empty recent stream. Symlink to excluded directory stays excluded. | Mapping feeds existing guards; supplemental. | **Labelled harness** for relative/invalid roots; create synthetic excluded/symlink destinations. Static transcript. Blank roots are filtered before this phase (R03), not treated as destinations. | **No body capture.** Existing exclusion/realpath tests support guards, not mapped-root variants. Not in minimum added set. |
| R09 — Destination absent/file/linked worktree | First mapped root missing, a regular file, or linked Git worktree: scan omits it. Direct `recentThreads` has no equivalent directory/worktree existence gate and may import if selected-root identity matches; do not assert it is always empty. A valid later root is not used as fallback. | Existing scan/recent asymmetry exposed to mapped roots; supplemental, no new validation promised. | **Labelled harness**, synthetic destination variants and explicit direct recent calls. Static transcript. | **No body capture.** General missing-directory/worktree tests cover scan only. Not in minimum added set. |
| R10 — Distinct homes and provider ownership | Two configured Codex homes with the same synthetic thread UUID but different project mappings P/Q; each maps locally and keeps its providerInstanceId. Aliases of one home scan once; disabled instances do not scan. Claude/Pi remain unaffected. | New DB read scoped to an existing per-home loop; supplemental ownership check. | Provider instance `config.homePath` or instance `CODEX_HOME` environment; two isolated synthetic homes. Labelled scanner harness, static transcript. | **No body capture.** Existing tests cover home resolution/dedup/ownership without DB project mappings; do not claim mapping-isolation runtime proof. |
| R11 — Age and bounds | Assigned S→P transcript >30 days old or future mtime: scan still counts; recent excludes. Exact cutoff is inclusive. Discovery/import/record budgets can truncate or skip; the new 100,000 joined-row cap silently leaves unmapped threads on old behavior, without setting a new truncation flag. | Inherited time/budget gates, new defensive DB cap; supplemental. | **Labelled harness** with injected clock and controlled mtimes/row counts. **Recording:** ordered clock/input/output trace, never wall-clock sleeps. | **No body capture.** Existing recent/budget tests support inherited gates; no mapped-clock/cap proof claimed. Not in minimum added set. |
| R12 — Repeated and completed import | Mapped chat imported twice or duplicate rollouts share one thread ID. Matching completed source yields `AlreadyImported`; another same-session file yields `Duplicate` (or is suppressed after completed history). Changed file identity is reparsed. | Existing import outcomes, now reachable by newly mapped chats; supplemental. | **Labelled harness:** retain returned source identity, call recent again with completedSources; optionally real import RPC for persistence. **Recording:** both calls and emitted outcomes. | **No body capture.** Existing duplicate/completed-identity tests support shared behavior. Not in minimum added set. |
| R13 — No readable cwd / no importable conversation | Empty/malformed/no-cwd file or Codex subagent is not rescued by project metadata. A valid mapped cwd with no resumable ID/visible message can add scan count yet yield `Skipped` on recent parsing. | Existing early exits/parser outcomes bypass or follow new mapping; supplemental. | **Labelled harness**, synthetic JSONL variants with optional DB row. Static transcript. | **No body capture.** Existing malformed/subagent/parser tests cover underlying behavior. Not in minimum added set. |
| R14 — Warm/cold calls and metadata changes | With stable inputs, scan→recent on one scanner and cold recent on a fresh scanner agree on mapped membership. Mapping changes after collection remain cached until a new scan refreshes candidates. Existing persisted project identity and directory aliases still merge. | Mapping now travels in cached transcript data; stable warm/cold check is part of each required capture, live cache-mutation behavior supplemental. | Real scanner driver runs both instances; **recording** of call sequence. Optional state edit then rescan phase uses a fresh synthetic DB update. UI alternative: `/welcome`, review/select project, import; must record import completion, not a still. | **Unspecified/partial.** Existing body prints both outputs but omits scanner-driver construction, so it does not establish warm versus cold instance lifetime. New moved-chat test creates separate scanner layers for its two calls. Fold stable warm/cold assertions into M1–M3; no extra UI run needed. |

## Minimum capture set

Viewport × theme × row is **N/A × N/A × service fixture**. One supported host and the actual scanner suffice; do not multiply server transcripts by desktop/mobile breakpoints. These captures are runtime transcripts, not unit-test logs. App-server-created data is preferred; synthetic SQL/JSONL fixtures must be labelled harness captures.

- **M0, already present:** R01 + R02, assignment sequence and base/head scanner outputs. Credit the existing PR body; do not repeat solely for the new commit SHA.
- **M1, missing:** one composite home containing R01 plus distinct R03/R04/R05 threads. Print scan candidates and recent(P), recent(Q), exact logical IDs, tags and inherited messages. Use a multi-rewind parent to exercise the second regex capture group in history lookup. Run warm scan→recent and cold recent with stable data. This can also cover R07 in a separate fresh-scanner phase. Keep distinct before/after labels when contrasting the pinned base and reviewed head; unchanged controls should match.
- **M2, missing:** make a new run directory with the same synthetic files but a corrupt state DB; print scan and warm/cold recent outputs. Verify the direct control survives and the excluded scratch chats remain excluded. No need to demonstrate every failure mode or wait 100 ms on camera.
- **M3, missing:** ordered multi-root fixture R07, print P-only membership and Q absence. Can be combined with M1 as a labelled additional phase; does not require a separate server.

Each driver run must write to a new timestamped directory beneath the designated audit scratch directory. Use absolute synthetic home/root paths, isolate all provider homes, keep the real process/project services where needed, inject settings/project-store fixtures as in the existing scanner test layer, and label those substitutions. Record commands and scanner source SHA/blob with the output. No real provider turn is necessary for harness variants.

The server-only before/after transcript already proves the issue's main outcome. The insufficiency verdict below is specifically against this requested pass-1 matrix, not a demand for unrelated visual/UI evidence or a finding that the implementation is broken.

FRICTION: none

VERDICT: INSUFFICIENT R03, R04, R05, R06, R07 (include warm/cold calls from R14 in the added transcripts)
