# Claude fork after project root change — runtime evidence

Before: [1688f96568](https://git.cafe/pingdotgg/t3code/commit/1688f965682d331da16a8c39f049592361f69c8d), fetched origin/main at start.
After: [c0fdbfd6b9](https://git.cafe/pingdotgg/t3code/commit/c0fdbfd6b9b6173a0adaf25aeb7e7f43f0f6b185).
The local `main` ref was older; the actual implementation base is the fetched commit above.

Environment: Linux x64 fs-13, Node 24.21.0, Claude Agent SDK 0.3.276, Claude Opus 5.5, default Claude home. T3 real web and server from the worktree. Browser: T3 collaborative Chromium 154 on Linux, no device emulation, DPR 2, 1280x800 CSS pixels, default font scaling, light appearance. Screenshots are supplementary runtime illustrations, not pixel-comparison evidence; no rendering code/dialog changed. Clock was not frozen; wall-clock timestamps identify persisted events and logs.

## Commands and fixtures

```sh
git -C ~/t3code fetch origin main
git -C ~/t3code worktree add ~/wt/issue-22796 origin/main
cd ~/wt/issue-22796
/home/ubuntu/t3code/node_modules/.bin/vp i
mkdir -p /tmp/issue-22796/project-a
git init /tmp/issue-22796/project-a
git -C /tmp/issue-22796/project-a -c user.name=Triage -c user.email=triage@example.invalid commit --allow-empty -m fixture
mkdir -p /tmp/issue-22796/project-b
cp -a /tmp/issue-22796/project-a/.git /tmp/issue-22796/project-b/
env -u T3CODE_HOME T3CODE_DEV_SEED=0 node_modules/.bin/vp run dev --home-dir /tmp/issue-22796/t3-home
```

The runner reported `serverPort=16718 webPort=8678 baseDir=/tmp/issue-22796/t3-home`. Paired browser using the startup link (credential omitted). Added A with Add project → Local folder. Selected Claude Opus 5.5 and sent `Reply with exactly ROOT_A_OK. Do not use tools.`

A and B are separate copies of the same empty repository: updating settings alone is a supported reproducer, no directory deletion is needed. No live T3 database was used. An initial command included an extra `--`, so the runner selected the worktree's isolated `.t3`; stopped it before reproduction and restarted with the explicit home above. No process outside this task was stopped.

## Observed runtime sequence

| Time UTC | Source version | Action | Observation |
| --- | --- | --- | --- |
| 11:18:56–58 | Before | Claude first turn at A | Native session `a2a3c689-10ab-4950-a272-ba2797def61e`, `cwd=A`; result `ROOT_A_OK`. |
| 11:21:02 | Before | `t3_project_update` sets `workspaceRoot=B` | Persisted `project.meta-updated`, sequence 71. |
| 11:21:24–26 | Before | UI: Fork from first response, then send `Reply FORK_OK. Do not use tools.` | Fork thread `eb614a78-2f50-437e-a3c1-259411dfecc8` accepted; first run failed. `session.fork` carried `dir=B`, selected assistant UUID `3b5335fb-9c3c-43a9-bb86-1d5f6209312c`; trace: `Session a2a3c689-10ab-4950-a272-ba2797def61e not found in project directory for /tmp/issue-22796/project-b`. |
| 11:21:41–52 | Before | Parent continues at updated project; calls `t3_thread_fork` at completed run 1, then `t3_thread_send` | Parent run 3 completed. MCP fork `mcp:66eca702-8b81-4bb1-893a-ca72c1a36607:fork` failed with the same error. |
| 11:22:23–26 | Before | Codex first turn at B | Completed `CODEX_ROOT_B_OK`. |
| 11:23:30 | Before | Codex updates root to A | Persisted sequence 253. |
| 11:24:01–04 | After | UI forks Codex run 1 after B→A | Completed `CODEX_FORK_OK`. Codex implementation is unchanged by this diff. |
| 11:25:45–48 | After | UI Claude fork while root=A | `session.fork` has no dir, copies same source cursor; first turn `cwd=A`, result `BASELINE_FORK_OK`. |
| 11:26:20 | After | Parent sets root to B again | Persisted sequence 370. |
| 11:26:56–11:27:00 | After | UI Claude fork of same first response, send `Reply FIXED_UI_FORK_OK and state the working directory from your system context. Do not use tools.` | Native fork `b43a7dba-2e3c-4f55-b324-a2e703e64467` created. `query.open` uses `resume=<fork>` and `cwd=B`. Completed `FIXED_UI_FORK_OK`, reported B. |
| 11:27:30–36 | After | `t3_thread_fork` from run 1, then `t3_thread_send` | MCP fork `mcp:5c779b34-39d5-427b-8107-a10747840e7e:fork` completed. Native fork `b48ed408-36da-4bec-a5b4-f786f23aac2a`; no fork dir, `query.open cwd=B`; result `FIXED_MCP_FORK_OK. My current working directory is /tmp/issue-22796/project-b. It used to be /tmp/issue-22796/project-a.` |

Additional controls replayed on the exact Before commit after stopping and restarting only the owned server:
- 11:30:48–51: original parent `query.open` resumed `a2a3c689-10ab-4950-a272-ba2797def61e` with `cwd=B`; completed `PARENT_RESUME_OK` and reported B.
- 11:31:01: project reset to A with `t3_project_update`.
- 11:31:15–18: UI baseline fork of the now-visible completed run 2 (cursor `fc8207c1-8b62-4a5b-8687-543f89227316`) succeeded. `session.fork dir=A`, native fork `28f6e671-e6b5-4142-ad4c-8b5fb6d6f615`, first query `cwd=A`, response `BEFORE_BASELINE_FORK_OK`. The earlier After baseline selected run 1; the differing cursor is explicit, and both inherited the known A marker.

The source transcript remains at `~/.claude/projects/-tmp-issue-22796-project-a/a2a3c689-10ab-4950-a272-ba2797def61e.jsonl`. The fork reads the native session ID and passes the current runtime cwd to the SDK as a lookup restriction; it does not read an old root from stored thread metadata. RuntimePolicy already resolves the current project root for non-worktree threads. Removing `dir` lets the SDK locate the session across project folders in the selected Claude home. Query resume still gets the current cwd.

[Sanitized persisted events, provider protocol excerpts and outcomes](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/41fdfc72bbab3de9/runtime-evidence.json).
Exact MCP calls follow in the companion JSON. No tokens, environment variables, raw prompts beyond this fixture, or complete session files are published.

## Scoped checks

```sh
node_modules/.bin/vp test run apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts -t 'original directory'
# Without implementation: 1 failed, 202 skipped; real SDK missing-session error for /workspace.
# With implementation: 1 passed, 202 skipped.
node_modules/.bin/vp test run apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts apps/server/src/orchestration-v2/Adapters/ClaudeAgentSdkQueryRunner.test.ts
# 205 passed in 2 files.
node_modules/.bin/vp run --filter t3 typecheck
# exit 0; existing diagnostic suggestions only.
node_modules/.bin/vp lint apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
# exit 0; preexisting unused 'layer' warning.
```

Rebuildable fixture: existing `ClaudeAdapterV2 native fork` test now writes an isolated two-entry real SDK transcript under `-old-workspace`, forks at its assistant cursor through the worker using a temporary CLAUDE_CONFIG_DIR, checks inherited text, and verifies resumed ID/current cwd. Test count remains 203 in that file: one rewritten, none added or deleted. No dependency patch, production regex, parser or loop added.

What else changed: one existing test replaced its fake fork with the real SDK fixture; no production behavior besides native Claude fork lookup changed. Supplementary screenshots differ in conversation title, sidebar thread count, timestamps, sent prompt and failure/success content because they record distinct runtime attempts. They are not presented as pixel-identical UI comparisons.

Unverified: mobile fork, Windows/macOS runtime, remote/relay connections, other providers beyond the Codex comparison, the no-cursor adapter fallback as a live runtime, and subagent-launch lookup (preexisting separate cwd pinning). No UI layout or fork dialog changed.
