# Runtime captures for [#22803](https://git.cafe/pingdotgg/t3code/pulls/22803)

**Environment:** machine fs-12, Linux 6.1.102 x86_64, Node v26.12.1, `@anthropic-ai/claude-agent-sdk` 0.3.276. BEFORE = [8d3b061121](https://git.cafe/pingdotgg/t3code/commit/8d3b061121fe65669d5d43d011b48cd3027a4108) (`8d3b061121fe65669d5d43d011b48cd3027a4108`), with the two evidence files copied in untracked. AFTER = [2fb0145392](https://git.cafe/pingdotgg/t3code/commit/2fb01453927cad79c5b7d54e74764500c2e54488) (`2fb01453927cad79c5b7d54e74764500c2e54488`), which is [e7f57a52be](https://git.cafe/pingdotgg/t3code/commit/e7f57a52be493928cfd0b79983a772b7c53011ef) plus those same two files committed. Stub `claudeQuestionMockCli.mjs` sha256 `d488d671516f2543c07a6adf3cc7c65521353d00adc33f68405c8786536b0067`, driver sha256 `eff31b46ffe6a08f54a8a9ba3c5aeedd96670c16f6b70a5a981fd58ddddce34c`. Both are byte-identical in the two trees. The only other source difference is the PR's `ClaudeAdapterV2.ts`/`.test.ts`. Captured 2026-10-22 UTC.

All rows are labelled **fixture: claudeQuestionMockCli**. No real Claude was run. The SDK, adapter, orchestrator, projections and WebSocket are all real: a real `bin.ts start` server, an isolated home under `/tmp/ev-22803/<rev>/home`, and `providerInstances.claudeAgent.config.binaryPath` pointed at the stub.

## Matrix

| Row | Trigger | Before | After | Discriminating? |
|---|---|---|---|---|
| A1 | `run.interrupt` while Q1 pending | Q1 pending@26 → cancelled@33, emitted in the terminal batch (same `occurred_at` as `provider-turn interrupted`@36). Run interrupted. No `control_response` for `cu_q1` | Q1 pending@26 → cancelled@31, adapter-emitted ahead of `provider-turn interrupted`@36. Run interrupted. One cancel, no duplicate | No: same end state (no regression) |
| A2 | `thread.stop` | pending@71 → cancelled@76 (same ms as terminal@79). Run interrupted | pending@71 → cancelled@76 (adapter, 16 ms before terminal@81). Run interrupted | No |
| A3 | stub exits 1 mid-question | pending@113 → cancelled@119 (same ms as `provider-turn failed`@122). Run failed. Stub `exit code=1` | pending@116 → cancelled@120 (adapter, 23 ms before failed@125). Run failed | No |
| **A4** | stub `control_cancel_request cu_q1`, turn alive, replacement Q2 | **At K (Q2 pending): stream pending = [toolu_q1, toolu_q2]; DB projection pending = [toolu_q1, toolu_q2]**. Q1 cancelled only at @174, after the Q2 answer, in the turn-completion batch (@177) | **At K: stream pending = [toolu_q2]; DB: Q1 `cancelled`, Q2 `pending`**. Q1 cancelled@160 < Q2 pending@166. No second Q1 cancel. Run completed | **YES**: the stale question is withdrawn only in AFTER |
| B | `runtime-request.respond` Blue | pending@212 → resolved@216, 0 cancelled. Stub got `allow answers={"Red or Blue?":"Blue"}` | pending@216 → resolved@220, 0 cancelled. Same allow | No (unchanged path) |
| C | stale `control_cancel_request cu_q1` after the allow | pending@260 → resolved@264, 0 cancelled | pending@264 → resolved@268, 0 cancelled | No (no spurious cancellation) |

Every row observed Q1 `pending` on the `orchestration.subscribeThread` stream before acting (`-- checkpoint Q1 pending (before action)` in each transcript).

## Excerpts

**A4 BEFORE** (client stream, IDs trimmed):
```
event seq=153 runtime-request.updated status=pending kind=user_input native=toolu_q1
-> touch A4.cancel (fixture sends control_cancel_request cu_q1)
event seq=161 runtime-request.updated status=pending kind=user_input native=toolu_q2
-- checkpoint K: Q2 pending, turn alive: stream pending=["toolu_q1","toolu_q2"]
-> dispatch runtime-request.respond
event seq=163 runtime-request.updated status=resolved kind=user_input native=toolu_q2
event seq=174 runtime-request.updated status=cancelled kind=user_input native=toolu_q1
event seq=177 provider-turn.updated status=completed
projection at K: user_input pending toolu_q1 | user_input pending toolu_q2
```
**A4 AFTER:**
```
event seq=156 runtime-request.updated status=pending kind=user_input native=toolu_q1
-> touch A4.cancel (fixture sends control_cancel_request cu_q1)
event seq=160 runtime-request.updated status=cancelled kind=user_input native=toolu_q1
event seq=161 node.updated status=cancelled kind=user_input_request
event seq=162 turn-item.updated status=cancelled type=user_input_request
event seq=166 runtime-request.updated status=pending kind=user_input native=toolu_q2
-- checkpoint K: Q2 pending, turn alive: stream pending=["toolu_q2"]
event seq=170 runtime-request.updated status=resolved kind=user_input native=toolu_q2
event seq=181 provider-turn.updated status=completed
projection at K: user_input cancelled toolu_q1 | user_input pending toolu_q2
```
**A4 stub trace (identical in both):** `out control_cancel_request cu_q1` → `in control_response cu_q1 behavior=deny message="User cancelled tool execution."` → `out can_use_tool cu_q2 toolu_q2` → `in control_response cu_q2 behavior=allow answers={"Red or Blue?":"Blue"}` → `out result success`.

**A1** (persisted `seq event status native occurred_at`):
```
BEFORE 26 runtime-request.updated pending  toolu_q1
       33 runtime-request.updated cancelled toolu_q1 ; 34 node cancelled ; 35 turn-item cancelled
       36 provider-turn.updated interrupted
AFTER  26 runtime-request.updated pending  toolu_q1
       31 runtime-request.updated cancelled toolu_q1 ; 32 node cancelled ; 33 turn-item cancelled
       36 provider-turn.updated interrupted
trace (both): out can_use_tool cu_q1 → in control_request interrupt → out control_response (interrupt) → exit code=0; no control_response for cu_q1
```
**A2 timestamps:** BEFORE cancelled `11:18:16.124Z` and terminal `16.125Z`. AFTER: Q1's `resolvedAt` is `37.458Z`, its event is `37.459Z`, and the terminal is `37.474Z`. A3 shows the same pattern.

**B/C trace:** `in control_response cu_q1 behavior=allow answers={"Red or Blue?":"Blue"}`. C then sends `out control_cancel_request cu_q1`, which the SDK ignores. Both revisions have zero `cancelled` events for Q1.

## Full transcripts

Each transcript has a summary, the client stream, persisted events, projection rows per checkpoint and the stub trace.

| Row | Before | After |
|---|---|---|
| A1 | [pr22803-A1-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f9ea958ace28b242/pr22803-A1-before.txt) | [pr22803-A1-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/46800796d9e43920/pr22803-A1-after.txt) |
| A2 | [pr22803-A2-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9ce3a5c60568896c/pr22803-A2-before.txt) | [pr22803-A2-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f39f8fbe3bbcd075/pr22803-A2-after.txt) |
| A3 | [pr22803-A3-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1a0e3a003bde7e8d/pr22803-A3-before.txt) | [pr22803-A3-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/574e851cb5a74488/pr22803-A3-after.txt) |
| A4 | [pr22803-A4-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1e43858aa7721b1a/pr22803-A4-before.txt) | [pr22803-A4-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/262e7ff0eee7a3ae/pr22803-A4-after.txt) |
| B | [pr22803-B-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/abd7135fd437e86d/pr22803-B-before.txt) | [pr22803-B-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/40856fcf99a791ff/pr22803-B-after.txt) |
| C | [pr22803-C-before.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d58b20c82710d85d/pr22803-C-before.txt) | [pr22803-C-after.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/57497425901f301b/pr22803-C-after.txt) |

The raw evidence ([pr22803-raw-evidence.tar.gz](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3e80d6d9d6e2a7b0/pr22803-raw-evidence.tar.gz)) has the per-row `*.stream.ndjson` (every subscribeThread item), `*.db.json` (checkpoints, final DB rows, `getThreadProjection`), the stub `traces/*.ndjson`, `server.log` (bootstrap token redacted), `manifest.json` and `results.json`, for both revisions. Isolated homes are excluded.

## What else changed / caveats

- **Matrix correction:** in both revisions the persisted log is `orchestration_events` (`stream_id` = thread id). `orchestration_v2_events` exists but is empty. Attribution therefore uses `metadata_json.driver`, `command_id` and timestamps, not `provider`/`raw_event_id`.
- **A1–A3 ordering:** BEFORE's single cancel trio carries the terminal batch's timestamp, which is consistent with ingestor dismissal (`dismissNativeUserInputs`). AFTER's trio lands 15–25 ms earlier with its own `resolvedAt` and comes from the adapter. The ingestor then finds nothing pending, so there is no duplicate. Both full AFTER runs on fs-12 (2× each of A1–A3) had the adapter first. The other ordering, which would produce a duplicate cancelled event, was not observed but is not ruled out.
- Sequence numbers, thread/request IDs (they embed random UUIDs), ports (BEFORE 40281, AFTER 34141), PIDs and timestamps differ between runs. Rows share one server per revision, so sequences keep climbing across rows.
- **Harness hazard found:** an ambient `T3CODE_HOME=/home/ubuntu/.t3` outranks the bootstrap `t3Home`. The first attempt was refused by the live server's ownership lock, and nothing was written. The driver now passes `--base-dir <home>` and strips `T3CODE_*` from the server env.
- The driver allows a 1.5 s settle window after the run goes terminal to catch late provider events. This is a fixed delay, not an event wait.
- Server PIDs were captured at spawn and killed by process group (stub children included). After both runs no server or stub PID was alive and the ports were free. The BEFORE worktree has been removed.

## Unverified

- Real Claude corroboration was not attempted. It is optional per the matrix, and A4 cannot be produced by a real model.
- Optional A1′ (steer-only) was not run.
- No UI rendering was captured. This pass is server and wire only.

## Reproduce one row

```
cd <worktree at 2fb0145392 or BEFORE + the two files>
node apps/server/scripts/claude-question-withdrawal-evidence.ts --worktree "$PWD" --home /tmp/ev-22803/after/home --out /tmp/ev-22803/after --row A4
```
