# Proof audit, pass 1: OpenCode command request timeout

PR [#27677](https://git.cafe/pingdotgg/t3code/pull/27677), branch `fix/issue-27664`.
Before: [f012fa210d](https://git.cafe/pingdotgg/t3code/commit/f012fa210dbf464a5ea68fe9071ee377682f8d66).
After: [134aea71f0](https://git.cafe/pingdotgg/t3code/commit/134aea71f0c861f362534d7ffeab390bfdb83d42).
Audit date: 2026-11-03 UTC. Read-only inspection; no captures were produced in pass 1.

## Findings

1. **Current-revision proof is missing.** The PR description's real-server before/after runs identify older commits. They are useful mechanism evidence, but cannot fill rows pinned to the requested merge-base and head. The complete diff is two added production lines (one comment and one guard condition) and one 80-line test; there is no UI component change.
2. **The new test proves only the settled-admission ordering.** It emits the matching user message, joins `startTurn`, then rejects `session.command`, then emits busy/idle and expects completion. It neither waits 300 seconds nor uses a real HTTP server or model. It is an appropriate focused regression test, but cannot replace M1's real-server transcripts. Test source was inspected, not executed in this pass.
3. **There are two callers and an additional ordering boundary.** `startTurn` and `steerTurn` both invoke `submitPrompt`. The new test covers start only. Also, `tapError` suppresses a side effect without recovering the request's error channel; `Fiber.join(request)` remains one contestant in `raceFirst`. An unresolved CodeRabbit comment on the current head raises the receipt-versus-request-settlement ordering. Source inspection establishes the error-propagation path, not that the disputed scheduling is reachable. M3 must supply a controlled harness result or a concrete scheduling/reachability explanation; this audit does not label the bot claim a confirmed defect.
4. **The added guard is broader than a headers timeout.** It ignores any SDK request failure after that request's receipt is done, provided the old guard conditions would not already suppress it. Provider errors, stream loss, and interrupt still have separate terminal paths. A live stream that never sends a terminal signal has no newly added post-admission deadline.

## Code-derived path inventory

- [submitPrompt and its guard](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L2938): trimmed text is formed from text parts; `^/([^\s/]+)(?:\s+([\s\S]*))?$` plus a matching entry in `command.list` selects the synchronous native command endpoint. Lookup failure or its 10-second lookup timeout falls back to an empty command list and therefore `promptAsync`. This lookup timeout is separate from command admission timeout.
- No slash prefix, unknown command, command mentioned later in text, and attachment-only input select `promptAsync`. Entirely empty/invalid input is rejected before `submitPrompt`. Text length, multiline arguments, file count, model, agent, and variant do not change the new predicate; the native command payload carries command/arguments, optional model/agent/variant, and file parts. They need no layout cross-product.
- [Matching receipt](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L2362): a `message.updated` event in a registered session, with role `user` and the exact generated command `messageID`, succeeds that request's deferred. Assistant messages and other IDs do not. General turn admission flags and this per-request receipt are distinct; assistant completion or HTTP success can advance general admission without succeeding this deferred.
- The changed condition is `receiptDone || aborted || finalized || generationChanged`. Only `receiptDone && !aborted && !finalized && !generationChanged` changes behavior. When every condition is false, the forked request error still finalizes failed. Existing abort/finalized/generation branches were already no-ops. Receipt success is never used to fail this deferred, so `isDone` here represents a successful matching receipt.
- [Admission race](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L3012): HTTP request completion or a matching receipt ends the race. If neither occurs within 10 seconds, abort the request, fail the turn, and return an admission error. A successful early HTTP response avoids this timeout even if the matching receipt has not yet arrived. The fork remains owned by the session scope; its `ensuring` removes receipt/controller entries on settlement.
- [startTurn](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L3278) installs the turn, submits, and has an outer `tapError` that can still finalize failed when submission returns an error. [steerTurn](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L3391) replaces the admission generation/ID/controller and submits into the same active turn; it reports submission errors through its steer error wrapper. There are no other direct `submitPrompt` callers.
- Actual app routing: `RunExecutionService` reaches session start through `ProviderSessionManager` and the delivery wrapper in `ProviderTurnStartService`; `ProviderTurnControlService.steer` reaches session steering through the manager. Client and agent/scheduled entry points that dispatch the same orchestration commands converge here; they do not need separate client captures for this server change.
- [Terminal events](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L2589): `session.idle` and idle `session.status` complete an admitted active turn (or interrupt it if marked interrupted). Idle during pending admission is held and can cause a status reconciliation when accepted and observed. `session.error` fails, except interrupt/`MessageAbortedError` produces interrupted; a 429 classifies usage limit. Global errors mark the thread broken. Finalization is idempotent.
- [Stream termination](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/provider-opencode/src/server/adapter.ts#L2683): unexpected EOF or stream failure fails active turns as `transport_error`, broken disposition; future starts are rejected. Managed OpenCode process exit also fails active turns. Deliberate runtime close follows separate cleanup. Closing/interrupting aborts tracked command controllers; interrupt invalidates the admission generation and calls `session.abort`.
- Exact text-only start `/compact` uses summarize before `submitPrompt`; OpenCode 2.x uses a different adapter selected by the version probe. The function name `makeOpenCodeAdapterV2` denotes T3's orchestration adapter generation, not OpenCode 2.x. These are scope exclusions, not proof of this fix.
- No components/props/layout branches changed. Theme, width, operating-system chrome, web versus desktop versus mobile, empty/loading visual states, and relay/tunnel connections do not branch this server predicate. The command endpoint is the T3-server-to-OpenCode connection; a client WebSocket disconnect is not an OpenCode event-stream disconnect. One real RPC client is sufficient for the core capture pair. Other providers are untouched.

## Fixture matrix

All temporal rows are **recording**. Here a recording means an ordered timestamped transcript, not a still image. `Required` marks the minimum proof set; other rows inventory unchanged branches and useful controls without demanding another five-minute run for each. No row below has a current-revision capture yet.

| ID / fixture | Data and event order | Diff effect / expected observation | Seed or drive method | Capture and status |
|---|---|---|---|---|
| **M1-B — long command before** | Known `/slow 330`; exact user receipt before 10 s; HTTP command response headers withheld naturally until generation ends; request fails near 300 s while native session remains busy; native idle later | Bug baseline: T3 fails at request timeout while OpenCode continues; later idle must not silently resurrect that run | **Real app**, base commit, fresh T3 home; register project command; RPC `message.dispatch`, `start_immediately`; real OpenCode 1.x/model | **Required recording — MISSING** |
| **M1-A — long command after** | Same command/model/runtime recipe as M1-B on head; correlate user receipt, actual HTTP rejection, native busy/activity, subsequent idle | **Changed:** request failure alone emits no failed terminal; run stays running past rejection, then completes from provider idle; one terminal outcome | **Real app**, head commit, independently fresh T3 home; same RPC path | **Required recording — MISSING** |
| **M2 — admitted steering request fails** | Active root turn, slash-command steering; matching steering receipt; wait for steer submission to settle; reject its HTTP request; then idle | **Changed** shared guard through second caller: current run survives HTTP failure and completes; steering admission succeeds | App route: `message.dispatch` with `steer_active`, target run. To force timing reliably, use a **labelled adapter harness capture** calling `steerTurn` with controlled SDK/events; queued native execution may otherwise miss admission deadline | **Required short head harness recording — MISSING**; base counterpart useful, no extra 300 s run needed |
| **M3 — receipt/request settlement boundary** | Matching user receipt becomes done; HTTP failure is handled before the admission race/caller has settled; all old guard conditions false; exercise start and steer return values | **Changed guard / unresolved ordering:** intended receipt-owned outcome must not be failed solely by request error. Trace which race branch wins and whether start/steer propagates an error | No app control deterministically exposes this scheduler boundary. **Labelled controlled adapter/Effect harness**, use explicit gates and real runtime scheduler; do not merely wait for start to return as the new test does | **Required short head harness recording or demonstrated unreachability — OPEN/MISSING** |
| C1 — request fails before receipt | Known command; no matching receipt; request rejects before 10 s; controller live, turn active, same generation | Unchanged: request error fails the turn; start reports failure (steer reports its error); no false admission | **Labelled harness** rejects command before emitting receipt. App-level fault proxy can fail only `/command`, but must be labelled injected | Recording — not captured; regression control |
| C2 — admission timeout | Known command; neither HTTP completion nor matching receipt for 10 s. Separately deliver only assistant/wrong-ID/unregistered-session messages | Unchanged: admission timeout aborts request, emits failed once, reports explicit admission error; those non-receipts do not release command race | **Labelled harness** with command gate and `TestClock.adjust`; no sleeps/polling. Can also delay just command response and matching SSE receipt through a labelled proxy to real app | Recording — not captured; regression control |
| C3 — interrupt/cancel | Interrupt once before receipt and once after receipt (including after the late HTTP rejection); pending request honors AbortSignal; idle or `MessageAbortedError` follows | Existing abort and generation guards suppress request-induced failure; final status interrupted. After admission, added receipt condition overlaps existing suppression when aborted | Real RPC `run.interrupt` on active run, or `thread.stop`; **labelled harness** for exact before-receipt ordering. Record server-side `session.abort`, not only UI stop | Recording — not captured; regression control |
| C4 — already finalized | Provider idle or error finalizes turn before outstanding command rejects; isolate with receipt not done where possible | Existing `finalized` guard: late request rejection produces no second terminal and does not change completed/failed result | **Labelled harness** controls HTTP settlement and native events; optional real short command with delayed proxy response | Recording — not captured; regression control |
| C5 — stale admission generation | Older command has no receipt; a newer steering admission changes generation; old HTTP request then rejects; newer admission is still active | Existing generation guard: old request must not fail current admission/turn; newer work later determines terminal | **Labelled harness** drives two admissions and gates old request; app supports steering but deterministic overlap is not promised | Recording — not captured; regression control |
| C6 — provider error after ignored HTTP failure | Receipt and settled submission; HTTP rejection; then `session.error` (ordinary error; 429; global error; aborted error as C3) | Added guard changes when/source of terminal; event handler unchanged: failed/provider_error or usage_limit; global error broken; aborted error interrupted | Real app can encounter provider errors but exact ordering/code is unreliable; use **labelled harness** with typed SSE events. No need to simulate each model | Recording — not captured; event-authority control |
| C7 — OpenCode event-stream loss | Active command, especially after receipt and ignored request rejection; stream EOF or iterator error. Separately terminate a captured managed OpenCode process | Stream/exit handling unchanged; active turn fails transport_error; stream-loss disposition broken; next start rejected after stream loss. This must still terminate an admitted command | Real app: close only its isolated OpenCode SSE connection with labelled fault proxy; or **labelled harness** EOF/error. Kill only a process captured at spawn, never a pattern-matched PID | Recording — not captured; event-authority control |
| C8 — successful native command | Known `/slow 1` or immediate command; HTTP resolves with receipt before/after response; busy/idle; separately stale idle before admission followed by reconciliation | Guard not invoked; unchanged success. Early HTTP success ends the 10 s race without needing a receipt; pending admission still waits for observation/reconciliation | **Real app** short command for normal success; **labelled harness** for exact response/receipt/stale-idle permutations | Recording — not captured; regression control |
| C9 — ordinary promptAsync | Plain prompt; unknown leading slash; slash command at end; attachment-only. Separate command-list failure/10 s lookup timeout. Exercise normal success and request failure | Guard bypassed; unchanged promptAsync success/error and interrupt behavior. Lookup timeout is fallback, not failed command admission | Real RPC message send with those text shapes and attachments; **labelled harness** for lookup faults. Only representative routing control needed, no layout variants | Recording — not captured; regression control |
| C10 — no terminal event after admission | Receipt, settled submission, request rejection, then live stream with no terminal. Finally explicitly interrupt or close stream to clean up | **Changed:** HTTP no longer supplies a terminal; no new post-admission timeout exists. Head stays active until independent terminal/interrupt/stream loss | **Labelled harness**, assert active snapshot at an explicit event/gate milestone, then interrupt; do not claim a finite transcript proves eventual liveness | Recording — not captured; diagnostic boundary, not a new liveness requirement |
| X1 — bypasses and non-branches | Text-only start `/compact`; empty invalid input; OpenCode 2.x; other providers; theme/width/client platform | No changed execution/layout path; summarize or validation or separate adapter applies | Inspect routing; no dedicated capture for this PR | **Excluded — no capture required** |

## Real-app seeding and recording contract

1. The lead owns checkout creation and server launch. This pass does not edit the worktree or start servers. For the later captures, pin full git SHA per run and use the requested base/head, not current moving `main`. All newly written files outside a worktree belong beneath `/tmp/lead-27677-r1/proof-audit-1/`; use a new timestamped run subdirectory, preserve raw logs, and never replace the previous run. Do not delete the lead's worktree.
2. Start each real T3 dev server with **`T3CODE_DEV_SEED=0`** and an explicit new `--home-dir /tmp/lead-27677-r1/proof-audit-1/<timestamp>/t3-home`. Seed only synthetic project/thread data through the app RPC. Do not seed from live user data, set baked Vite origins, or share startup pairing URLs in public evidence.
3. Use a tiny disposable project under that run directory, with `.opencode/command/slow.md`: `Use the bash tool exactly once to run sleep $ARGUMENTS && echo slow-done (set bash tool timeout to 900000 ms). Then reply with exactly: DONE`. Format the shell command with backticks in the actual fixture. Enable full-access permissions for this synthetic project so an approval prompt does not become the reason it runs for five minutes. Confirm `command.list` actually advertises `slow`, and verify the model really ran the sleep; model instructions alone are not proof.
4. Enable the OpenCode provider instance through the dev app's provider configuration; use a real 1.x `opencode serve`, either T3-managed (`serverUrl` blank) or a dedicated externally launched instance (`serverUrl` loopback URL, process captured at spawn). Keep binary/server/model settings identical across M1-B/A. An external isolated server can preserve native activity after T3's baseline failure. Do not reuse another thread's server. The installed binary reports **1.18.18**. `opencode models` was read during this audit and lists **`anthropic/claude-haiku-4-5`**, **`anthropic/claude-haiku-5-5`**, and free OpenCode models, among others. Listing is not a successful model call; preflight the selected economical model and record the actual selection and success. Hub routing was not verified in this pass.
5. Create/register the disposable project using the app project RPC (for a new directory, `projects.createNew`), then use `orchestration.dispatchCommand` with `thread.create` and `message.dispatch` / `dispatchMode: { type: "start_immediately" }`. Subscribe before send; unique command/message/thread IDs and the configured provider `instanceId`/model are required. The route and contract are in [orchestrationV2.ts](https://git.cafe/pingdotgg/t3code/blob/134aea71f0c861f362534d7ffeab390bfdb83d42/packages/contracts/src/orchestrationV2.ts#L2866). Steering uses `dispatchMode: { type: "steer_active", targetRunId }`; interrupt uses `run.interrupt`. Use RPC schema encoding for DateTime/branded fields instead of guessing the WebSocket envelope. A standalone SDK call to OpenCode is mechanism evidence only, not a real T3 run.
6. **Recording contents:** UTC timestamps and relative offsets for command dispatch, generated native message ID, matching user receipt, submission acceptance, actual command HTTP rejection and cause, native ongoing activity/busy, provider idle/error, persisted T3 run/turn terminal, and final snapshot. Correlate run/attempt/provider-session/native-session/message IDs. Include code SHA, Node version, OpenCode/SDK version, model, command source, launch flags, and driver description. Require exactly one final outcome and show that late idle does not change the baseline failed run. Record both the native stream and committed app events; a client spinner or final UI frame is only optional corroboration.
7. The existing `sdkCall` logs outgoing requests and successful responses, **not rejected request causes**. A missing incoming response plus a roughly 300 s failure is an inference. Where practical, add an external Node diagnostic/preload observer for the actual command request's sanitized undici error/cause, without editing production code or changing timeout behavior. Filter to this isolated command request; never log auth headers, credentials, hub tokens, or pairing URLs. If only a separate mechanism probe captures the exact cause, label it separately and do not imply it observed the T3 request. Using Bun for the T3 server or disabling headers timeouts would invalidate the natural Node timeout reproduction.
8. Minimum artifacts: **M1-B and M1-A real recordings**, plus **M2 and M3 short labelled harness records** (M3 may instead be closed by demonstrated unreachability under the actual scheduler). The unchanged C rows are an inventory/control set, not a demand for a full runtime permutation suite. Existing focused tests may cover them; specifically, the new regression test covers settled start admission, the existing native-command test covers receipt admission, and existing interrupt/stream-EOF tests cover those mechanisms generally, not necessarily native-command-specific permutations. Tests were not rerun and no capture is claimed here.
9. Harness records must identify mocked endpoints/events and use the actual adapter at the named revision. Use event/Deferred/Queue milestones and TestClock for admission timeout; never sleep or poll to force a race. Every new run gets its own timestamped output directory. Upload only sanitized synthetic evidence. Keep raw logs until uploaded. Check `df -BG --output=avail /`; stop if less than 5 GB. This audit observed 19 GB available.

## Coverage and limitations

Covered the full base/head diff, changed test and stream helper, both direct submit callers, their orchestration routing, receipt matching, admission state/reconciliation, interrupt, finalization, provider errors, stream termination, process exit, promptAsync fallback, runtime generation selection, PR description, linked issue, comments/reviews, and current status through cafe. No browser/computer use, model turns, tests, servers, PR writes, edits, pushes, or comments were performed. Only scratch report/metadata files were written. The worktree remained clean.

FRICTION: none

VERDICT: INSUFFICIENT M1-B, M1-A, M2, M3 need current-revision recording/proof
