# Proof audit pass 1 — OpenCode overflow handling

PR [#26743](https://git.cafe/pingdotgg/t3code/pull/26743), issue [#26674](https://git.cafe/pingdotgg/t3code/issues/26674). Reviewed head [dd3b029f53](https://git.cafe/pingdotgg/t3code/commit/dd3b029f5325ae8e67ca89f37c426bdbe92449e6), branch `fix/issue-26674`, against the requested baseline [e34067edef](https://git.cafe/pingdotgg/t3code/commit/e34067edef2eba03c1e40bca7d2b2d933ae12b85). Use these exact SHAs for captures; do not substitute the moving or locally stale `main` ref. Forge metadata confirms the requested head. The complete pinned diff has only `adapter.ts` and `adapter.test.ts` (26 adapter lines added, one removed; two new tests).

This is a fixture specification, not a completed reproduction or a merge-readiness verdict. No new runtime captures or tests were run during pass 1. The existing uploaded transcript and drivers were read. They observe only OpenCode, not T3. CI was green when inspected, which does not replace the missing outcome evidence.

## Applicable paths and callers

There is no changed React component, layout, prop, route, interaction control, theme, width, platform-specific client code, or contract. Viewport × theme is **N/A × N/A** for every row. A terminal screenshot cannot prove event ordering. Every row below is a **recording**, meaning an ordered text/NDJSON runtime or test transcript, not necessarily video.

The changed data is an internal `contextOverflow: ProviderFailure | null` field, initialized separately for root turns and native OpenCode child turns. Its meaningful dimensions are error name, presence/ownership of session ID, existence of an active turn, root versus native child, successful compaction after the last overflow, admission state, and which terminal event wins. Error message text is preserved, not classified by length or substring.

- Production entry: [driver.ts](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/driver.ts#L250) constructs the 1.x adapter and selects it after a version probe; the OpenCode 2 adapter is separate and unchanged. Unit tests call `makeOpenCodeAdapterV2` directly.
- [Root construction and entry points](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.ts#L3135): `startTurn` handles normal messages and native slash commands; `compactThread` delegates to it with `/compact`. Exact `/compact` without attachments invokes `session.summarize`, bypassing normal prompt admission. `steerTurn` reuses the active turn and starts a new admission generation.
- [Native child construction](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.ts#L2198): task-tool projection registers a native child session; a new user `message.updated` creates its active turn. A T3 delegated thread using OpenCode is a **root adapter turn** in its own runtime; do not confuse it with this native child branch.
- [Event dispatch](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.ts#L2525): `session.error` defers only exact `ContextOverflowError` with a defined session ID matching an active turn. A matching `session.compacted` clears that turn's pending error and emits a compaction item. Unknown/inactive session compaction does nothing. All other errors follow the existing scoped/broadcast path.
- [Finalization](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.ts#L2014): callers are idle via `session.status`, legacy `session.idle`, admission reconciliation's `session.status()` result, summarize success/failure, native command failure/admission timeout, prompt submission failure, native stream EOF/error, and owned-server exit. `interruptTurn` sets the interrupt flag and aborts; a native event subsequently finalizes. Existing stream failure takes precedence; only attempted `completed` is converted by a pending overflow. Existing `failed`/`interrupted` outcomes remain their own outcome. Finalization is idempotent.
- Root finalization emits `provider_turn.updated`, `provider_session.updated`, then `turn.terminal` with failure and disposition. **Native child finalization returns after `provider_turn.updated` and `node.updated`; it emits no root `turn.terminal` and no failure object of its own.** Capture the child turn/node separately rather than demanding a nonexistent event.
- The production consumer [ProviderEventIngestor.ts](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/apps/server/src/orchestration-v2/ProviderEventIngestor.ts#L702) persists a failure item for a failed root `turn.terminal`. An adapter capture proves emitted T3 outcome; it does not prove database persistence, delegated-task reporting, server ownership cleanup, or client rendering.

## Evidence contract for every paired run

Use fresh synthetic native/T3 sessions for each baseline/head run, the same scenario/model config, and a new timestamped directory. Record exact T3 SHA, OpenCode version (the earlier driver used 1.18.18), scenario, model/config, session ID → provider thread/turn ID mapping, and action boundaries. Do not use the earlier PR table's different baseline as this pass's baseline.

One ordered log should correlate native events with T3 provider runtime events. Save raw native SSE and fake-model request log too. At minimum retain all `provider_turn.updated`, `provider_session.updated`, `turn.terminal` fields, compaction items, and the post-compaction assistant item. Include terminal count, `status`, full `failure` (`message`, `code`, `class`), `threadDisposition`, and session `status`/`lastError`; do not reduce outcomes to an assertion saying “passed.” Use emitted session updates for `lastError`, not a potentially stale initial runtime object.

For completed/interrupted root terminals require `failure: null`. For pending overflow failure require `class: provider_error`, `code: ContextOverflowError`, and the last recorded overflow's message; disposition defaults to `reusable`. A broken stream requires `class: transport_error`, disposition `broken`. Preserve actual messages rather than inventing expected text. Post-compaction text must carry the same T3 provider turn ID. Show the whole sequence through native idle, including the duplicate legacy idle if present; do not stop consuming at the first T3 terminal on the baseline. This exposes orphaned native continuation and guards against duplicate terminals.

## Fixture matrix

“Changed” describes code behavior, not whether an assertion must fail on the baseline. `LIVE` = T3 adapter against unmodified real `opencode serve` and a fake OpenAI-compatible model. `UNIT` = explicitly labelled synthetic native-event harness. Both are recordings. Baseline/head outcomes below are **predictions from code to verify**, not observations.

| ID / named fixture | Required data and ordered trigger | Changed? | Before: required T3 evidence | After: required T3 evidence | Seeding / capture source; minimum proof |
|---|---|---|---|---|---|
| R1 Recovering root | Admitted root turn, `fake/fake`; first ordinary generation HTTP 400 `context_length_exceeded`; summary succeeds; subsequent agent text succeeds; `compacted → idle` | Yes, primary regression | One failed terminal at overflow; original provider failure; native summary and continuation still occur, without attaching continuation to the old T3 turn | No failed terminal at overflow; completed compaction item and post-compaction assistant text on original turn; exactly one `completed` terminal at idle, null failure, ready/null-error session | **LIVE recording, mandatory paired capture.** Reuse the earlier fake-model behavior, but submit through T3 adapter `startTurn`, not native `prompt_async` alone. Gate the summary/continuation so ordering is observable. |
| R2 Uncompacted root | Same root/model; `compaction.auto=false`; first agent request overflows; native idle; no `session.compacted` | Yes, timing; final status preserved | Failed immediately with overflow payload | Remains active until idle; failed with same overflow payload, `reusable`, session error and overflow `lastError`; one terminal despite both idle forms | **LIVE recording, mandatory paired capture.** Separate config/run with auto compaction disabled. This is distinct from a failed summary. |
| R3 Summary also overflows | Auto enabled; initial agent request overflows; the compaction request also returns context overflow; no successful `session.compacted`; idle | Yes, stored error can be replaced | Failed on first overflow | Failed only on settlement; pending failure is the **last `session.error` payload**, not an invented or later assistant-message error; session error reflects it | **LIVE recording, mandatory paired capture.** Add an explicit fake-model “compaction overflows” mode. Record both model requests and both native errors if emitted. Do not substitute R2. |
| R4 Stop during recovery | After first overflow, gate summary response before compaction succeeds; request T3 `interruptTurn`; record abort acknowledgment and native aborted/idle events | Yes, formerly already finalized | Old turn already failed; normal interrupt attempt may be rejected as not active (record the actual call result); original terminal stays failed | Interrupted terminal, null failure; pending overflow must not overwrite stop; native session aborts; one terminal | **LIVE recording, mandatory paired capture.** Trigger from recorded overflow boundary, not a guessed delay. Release/close fake-model gates on cleanup. A stop after `compacted` may also be demonstrated but does not replace this row. |
| R5 Broken stream during recovery | Admitted root; overflow; before compaction/idle, SDK event iterable ends cleanly or throws | Yes, transport failure wins while turn remains active | Existing overflow failure is already terminal; subsequent EOF cannot rewrite it | One failed terminal with transport failure and `broken`; no successful turn/compaction afterward; further start rejected for dead stream | **UNIT recording, mandatory paired guard.** Existing EOF test covers generic EOF but not EOF after overflow. A controlled live SSE/proxy fault is optional and must show the SDK iterable actually terminated: TCP loss alone may reconnect. An owned-server kill exercises a separate exit handler, not proof of EOF precedence. |
| R6 Known session, no active turn | Let a registered root settle; subsequently deliver overflow for that session; optional late compacted/idle | No, guard fallback | Session error/lastError updates; no new turn terminal and settled turn not rewritten | Same | **UNIT recording, paired guard.** Could also be LIVE by using native SDK to prompt the known session behind T3 after settlement; label that an out-of-band harness, not a normal T3 app action. |
| R7 Unknown / related-only session | Keep owned root busy; overflow and compacted for unknown native ID; separately related child with no registered `threads` entry | No, lookup must not broaden to owner | Owned active turn unaffected; no terminal/session failure attributed to it | Same; unknown compaction must not clear owned root's pending overflow | **UNIT recording, paired guard.** A second real native session can exercise unknown-ID events; related-only routing is easiest synthetically. Include an owned pending overflow then foreign `compacted → owned idle` to prove isolation. |
| R8 Missing session ID | At least one active root (two states if checking broadcast); overflow event with absent `sessionID` | No, bypasses new deferral | Existing broadcast failure on active roots, code ContextOverflowError, `broken` disposition | Same immediate broadcast failure; do not wait for compaction | **UNIT-only recording, paired guard.** No established real model/OpenCode route for an unscoped session overflow; do not fake a live claim. |
| R9 Ordinary session error | Active root; native `APIError`/other non-overflow error; include normal failure and separate `MessageAbortedError` control | No, existing error path | Ordinary error immediately failed with native error payload and reusable disposition; aborted error interrupted/null failure | Same | **LIVE recording mandatory for one non-overflow failure** using a nonretryable fake-model error, verifying its native name; **UNIT guard** for absent error payload, missing message (fallback to error name), aborted error, and a non-overflow error arriving after a pending overflow. |
| R10 Native task child recovers / fails | Root model issues real OpenCode `task`; child gets admitted native user message; child overflow; (a) compacted→idle or (b) idle uncompacted | Yes, second constructor and non-root finalizer | Child provider turn/node fails at overflow; no child root terminal; root's eventual outcome is separate | (a) Child provider turn/node completed and output stays on that turn; (b) child failed at idle; no invented child failure object/root terminal | **UNIT recording, paired guards minimum.** Real tool-call fake-model mode can exercise both if implemented; label LIVE only then. Existing two new overflow tests do not cover this constructor. T3 delegated-root R1 does not cover native task children. |
| R11 Explicit `/compact` | First seed native conversation; T3 `compactThread` or exact `/compact` without attachments; summarize succeeds, or summary itself overflows without compacted | Yes for overflow on this completion caller | Success completed; overflow immediately failed | Success completed; overflow pending until summarize settlement/idle, then failed with stored overflow (unless summarize RPC itself fails first, in which case actual RPC failure wins) | **LIVE recording recommended; UNIT paired guard minimum.** Explicit summarize is separate from R3 automatic compaction. A summarize RPC returning success with no compacted/idle while overflow is pending is a synthetic return-order fixture. |
| R12 Ordinary success / healthy compaction | No overflow, normal prompt→idle; separately successful `session.compacted` while active; later second ordinary turn in same session | No, null-state control | Completed/null failure, normal compaction item when appropriate | Same; the later turn starts with no stale overflow | **UNIT recording, paired controls minimum**; LIVE ordinary success can be folded into the next-turn portion of R1 or R2. Include late compacted with no active turn: no resurrection. |
| R13 Repeated overflows and clear ordering | Same turn: O1→O2→idle; O1→compacted→O2→idle; O1→compacted→O2→compacted→idle | Yes, replacement/clearing | Failed on O1 in every sequence | Failed with O2 in first two; completed/null failure in last; exactly one terminal per turn | **UNIT recording, paired guards minimum.** Real model can emit multiple errors but arbitrary same-turn ordering is not established; do not call synthetic events live. |
| R14 Admission and legacy idle variants | Pending admission plus overflow; idle before supplied user receipt; then receipt/acceptance and status reconciliation. Separately admit by completed assistant under provider-assigned ID; use legacy `session.idle` without status-idle | Yes through shared finalizer | Overflow has already failed turn | Early idle is held; reconciliation/legacy idle eventually fails if overflow remains, completes if compacted; no premature/duplicate terminal | **UNIT-only recording for exact races/orderings.** Use existing promise gates, Deferred, and event-stream handling acknowledgments. Existing admission tests do not combine overflow. No sleeps/time-based assertions. |
| R15 Other finalizer precedence | Pending overflow then non-overflow native error, prompt/command SDK rejection or command admission timeout, summarize rejection, or stream failure racing summarize success; interrupt flag then overflow | Yes, precedence while active | Original overflow has already failed; later events do not rewrite it | Existing specific failure wins for explicit failed status; interrupted wins unless native stream failure exists; native stream failure wins even against summarize completion; overflow cannot convert these to a completed turn | **UNIT recording, paired guards minimum**; use one case per distinct failure class/disposition, reuse existing finalizer/race tests. Command timeout uses TestClock; exact races are not live-model claims. |

## Minimum capture set and what unit tests can establish

The minimum **live** set is `{R1, R2, R3, R4, R9 ordinary failure} × {baseline, head} × {viewport N/A} × {theme N/A}`: ten runs, each a runtime transcript with T3 outcomes. R1 is non-negotiable regression evidence. R2 and R3 distinguish disabled compaction from failed compaction; R4 proves interrupt precedence under the changed behavior; R9 guards the deliberately unchanged error path. A single combined artifact can contain these ten clearly separated recordings.

The rest of the table needs labelled unit-harness evidence, paired where outcomes/timing change, or an honestly demonstrated live equivalent for the rows where one is described. These are guard **fixtures**, not a demand for one new test per row or a broad test-suite expansion. Reuse/parameterize existing cases. Exact missing-session-ID payloads (R8), delayed admission/status orderings (R14), and controlled completion/error races (part of R15) only have an established synthetic route. Deterministic SDK EOF after overflow (R5) is a unit fixture unless a live proxy experiment proves real termination. Known inactive/unknown sessions and native children are not intrinsically unit-only; they simply lack a normal app action or ready driver in the provided evidence.

Existing focused evidence in this revision is narrower: new test at [adapter.test.ts:540](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.test.ts#L540) asserts root recovery and retained text; new test at [adapter.test.ts:645](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/adapter.test.ts#L645) asserts root uncompacted failure/message. Neither asserts full failure shape, session lastError, or duplicate terminal count. Existing generic interrupt, EOF, compaction/EOF race, native compaction, admission, and ordinary-error usage cases help, but are not proof of the overflow combinations in R3–R15. Pass 1 has not independently rerun them or proved that those guard combinations pass.

## Real driver feasibility and honest labels

**A real T3 dev server is feasible in principle from code, not demonstrated here.** Start only isolated `T3CODE_DEV_SEED=0 vp run dev`; never use live userdata. Configure an enabled OpenCode 1.x instance with its `serverUrl` set to the isolated real `opencode serve` that was started with the fake-provider config. The custom config belongs to that OpenCode process, not to T3's arbitrary environment. Alternatively supply the instance environment's `OPENCODE_CONFIG`/`OPENCODE_CONFIG_CONTENT` for a T3-managed OpenCode process. Verify the 1.x version/probe selects the changed adapter.

The [settings schema](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/settings.ts#L31) exposes serverUrl and supports customModels. The [status loader](https://git.cafe/pingdotgg/t3code/blob/dd3b029f5325ae8e67ca89f37c426bdbe92449e6/packages/provider-opencode/src/server/status.ts#L833) merges the real server's model inventory with customModels. Thus the previous missing picker entry is not proof that app driving is impossible. Through the authenticated server RPC, set provider settings/model selection, create a synthetic project/thread and dispatch a normal message or compact/interrupt command, then await persisted turn/domain events or read back the thread/run state. A browser is unnecessary. Use the actual contracts for dispatch/readback; do not invent a `turn.start` endpoint. Record failure turn-items as well as final run/provider-turn status. This route can demonstrate persistence and, if actually exercised, delegated-task outcome; this pass does not claim either.

**Recommended first capture: labelled adapter integration harness against real OpenCode.** Use the production `makeOpenCodeAdapterV2`, live `OpenCodeRuntime.layer`, and its real SDK client. Provide isolated host paths plus IdAllocator/McpProviderSessions layers, then `openSession → ensureThread → startTurn` with model `fake/fake`. Subscribe to T3 runtime events before the turn. Feed model HTTP responses only; do not manufacture `session.error`/`compacted`/idle in a LIVE recording. Host/service scaffolding does not make the OpenCode event stream fake. An external `serverUrl` deliberately bypasses T3 ownership of the subprocess and external T3 MCP injection; disclose those limits. Do not claim this proves orchestration persistence, a parent delegate notification, or process cleanup.

The earlier driver does **not** implement this: it creates the native session and calls native `prompt_async` directly, logs native SSE, waits a fixed duration, then stops its processes. Adapt its synthetic model/config, not its claim of T3 coverage. For baseline capture, do not terminate collection at T3's first failed terminal: keep consuming until the provider idles so orphaned continuation is visible. Synchronize actions on recorded events/model gates; avoid blind delay-based success claims. The earlier fake-model matching of title/summary prompts is a starting point, not a guarantee across versions: log the classified request kinds and verify summary requests actually hit the intended mode.

Upstream reachability check: OpenCode 1.18.18's [processor](https://github.com/anomalyco/opencode/blob/v1.18.18/packages/opencode/src/session/processor.ts#L570) explicitly publishes overflow then idles when auto compaction is disabled; otherwise it requests compaction. Its [prompt loop](https://github.com/anomalyco/opencode/blob/v1.18.18/packages/opencode/src/session/prompt.ts#L1247) starts automatic compaction. Its [compaction implementation](https://github.com/anomalyco/opencode/blob/v1.18.18/packages/opencode/src/session/compaction.ts#L423) stops when summarization itself needs further compaction. These support R1–R3 driver feasibility, not a claim that their T3 before/after outcomes were captured.

## Handoff and limits

Covered: full pinned diff, root/native-child constructors, all finalizer callers, idle/admission/error guards, production 1.x selection, settings/model discovery, event ingestion boundary, existing focused tests, PR/issue metadata, and prior public evidence/drivers. No repository edits, pushes, comments, servers, browsers, additional worktrees, or test runs. No before/after evidence newly produced in this pass. Approximate elapsed time is recorded in the step's final response; the 60-minute budget was not exhausted.

Missing capture acceptance: live paired R1/R2/R3/R4/R9, plus labelled guard recordings R5–R8 and R10–R15 (R9 error-shape/abort variants included). All outcome predictions still require evidence. This is an evidence insufficiency verdict, not a confirmed implementation-defect finding.

FRICTION: tool | Python urllib received HTTP 403 for the public evidence download; curl retrieved the same files successfully | use curl for ghdrop asset downloads on this host.
