# OpenCode free-limit reset: manual real-app comparison

Scope: provider-boundary fault injection, not natural exhaustion of a live quota.

Both sides used separate unmodified application worktrees, their own fresh database
(T3CODE_DEV_SEED=0), Chromium at 1280x800 CSS pixels, light theme, UTC, and
OpenCode 2.0.28. The same synthetic git workspace was added with the T3 project CLI.

The attached limit-proxy.mjs ran between each T3 server and a real OpenCode 2.0.28
server. It forwarded HTTP and SSE unchanged except for execution terminal events:
it replaced the outcome with provider.free-limit / HTTP 429 / "Free usage limit
reached." / retryAfterMs. The delay was computed from each original event.created
so that both sides reported the same reset, 2026-11-11T12:00:00.000Z.
No T3 code, adapter, orchestration event, database row, or browser-rendered content
was patched. Successful upstream text can therefore precede the injected failure.

Seed commands, run from each application's checkout, with HOME_DIR and WORKSPACE
pointing at isolated synthetic state and FREE_MODEL selecting an available free
OpenCode model:

```sh
node apps/server/src/bin.ts project add "$WORKSPACE" --title 'Free allowance' --base-dir "$HOME_DIR"
node apps/server/src/bin.ts thread start "$WORKSPACE" \
  'Check the free allowance. Reply OK only.' --title 'Free allowance reset' \
  --provider opencode --model "$FREE_MODEL" --base-dir "$HOME_DIR" --wait --json
```

Each fresh settings.json enabled only OpenCode, with its serverUrl set to the
local proxy (port 14991). The upstream server listened on port 14990. A scratch
password was passed by each app to the upstream; no credentials are attached.

Browser interaction on both sides, using T3's Browser panel:
1. Pair with the isolated app, open the seeded thread, collapse thread details.
2. Start a recording.
3. Focus [data-testid="composer-editor"], insert `Reply OK only.` using
   document.execCommand('insertText', false, 'Reply OK only.').
4. Click button[aria-label="Submit message"].
5. Wait for the completed upstream response and injected terminal failure.
6. Stop the recording. Blur the composer and capture the resulting state.

Before: "Provider error", no reset time, no reset recovery actions.
After: "Usage limit reached", "Resets 12:00 PM", "Resume at reset", and
"Snooze until reset".

The extra head recording exercises Resume at reset, Snooze until reset, Wake now,
and Cancel auto-resume. Labels change to Resumes 12:00 PM / Cancel auto-resume /
Wake now and then back. All schedules were cancelled before teardown.

Screenshots were identical in two captures more than 500 ms apart (pixel diff 0).
The base's dismissible error toast was closed before the final still, leaving its
timeline errors visible. Captures were viewed before upload. Images and videos
are cropped identically to the chat area above the model selector, removing
private operational metadata. The viewport itself was unchanged.

Not verified: natural exhaustion or the live service's reset timestamp; automatic
execution at the future reset; native desktop and mobile clients.

AI-assisted; verified with the tests and tools listed above.
