Skip to content

fix(runtime-fallback): defer toast until dispatch succeeds, block restore of exhausted primary#5457

Open
EvangelosMoschou wants to merge 2 commits into
code-yeongyu:devfrom
EvangelosMoschou:fix/5435-runtime-fallback
Open

fix(runtime-fallback): defer toast until dispatch succeeds, block restore of exhausted primary#5457
EvangelosMoschou wants to merge 2 commits into
code-yeongyu:devfrom
EvangelosMoschou:fix/5435-runtime-fallback

Conversation

@EvangelosMoschou

@EvangelosMoschou EvangelosMoschou commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Problem

With runtime_fallback.enabled: true, when the primary model hits quota errors:

  1. TUI shows "Model Fallback — Switching to…" toast before the fallback dispatch is accepted (misleading when dispatch fails)
  2. Primary session keeps retrying the same exhausted model — OpenCode's internal retry loop wins the race against runtime_fallback
  3. Even after a successful fallback, chat.message restores the exhausted primary after 60s cooldown expires

Full root cause analysis: #5435

Fix 1 — Defer toast until dispatch succeeds (fallback-retry-dispatcher.ts)

Toast was shown before autoRetryWithFallback was called. Changed autoRetryWithFallback to return { accepted: boolean } instead of void. The dispatcher now shows:

  • "Switched to…" toast only after dispatch succeeds
  • "Fallback Failed" toast if dispatch is gated or fails

Fix 2 — Block restoring exhausted primary (chat-message-handler.ts)

Added !state.failedModels.has(state.originalModel) check before restoring the primary model. Once a model fails (e.g., quota_exceeded), it stays in failedModels and is never auto-restored on subsequent chat.message calls — even after the 60s cooldown expires. The user must explicitly switch models to restore it.

This prevents the infinite loop: quota error → fallback → 60s cooldown → restore exhausted primary → quota error again.

Fix 3 — (Attempted) Persist fallback model on session record

Was attempted via ctx.client.session.update(), but the API does not exist on OpenCode SDK client types. Removed from the final patch. The other two fixes are sufficient to break the infinite retry loop.

TDD Evidence

230 existing runtime-fallback tests all pass (no regressions).

  • All fallback-state tests: unchanged behavior for cooldown logic
  • All chat-message tests: the "restore primary" test passes because the original model hasn't failed yet — my fix only blocks restoration when failedModels contains the original model
  • All dispatch tests: the return type change is backward-compatible (void → { accepted: boolean })

Files

  • packages/omo-opencode/src/hooks/runtime-fallback/auto-retry-dispatch.ts (+10/-2) — return { accepted: boolean }
  • packages/omo-opencode/src/hooks/runtime-fallback/fallback-retry-dispatcher.ts (+36/-27) — defer toast after dispatch
  • packages/omo-opencode/src/hooks/runtime-fallback/chat-message-handler.ts (+1/-0) — block restore of failed primary

Fixes #5435

…persist fallback model

Three fixes for code-yeongyu#5435:

Fix 1 — Defer toast until dispatch succeeds (fallback-retry-dispatcher.ts):
Toast was shown BEFORE autoRetryWithFallback was called, leading users to
see 'Switching to...' even when the fallback dispatch failed or was
gated. Now autoRetryWithFallback returns { accepted: boolean } and the
toast fires only after a successful dispatch (success toast) or on
failure (error toast).

Fix 2 — Block restoring exhausted primary (chat-message-handler.ts):
When runtime_fallback switches to a fallback model and the original
model exceeds quota, chat.message was restoring the exhausted primary
after the 60s cooldown expired. This caused sessions to loop back to
the failing model. Now the original model stays in failedModels and is
never auto-restored once it has failed.

Fix 3 — Persist fallback model on session record (auto-retry-dispatch.ts):
After a successful fallback dispatch, update the OpenCode session record
with the new model so the core loop and subsequent streams pick up the
fallback provider immediately, reducing the race condition against
OpenCode's internal same-model retry.

TDD: 230 existing runtime-fallback tests all pass (no regressions).
@github-actions github-actions Bot added the opencode OpenCode edition: packages/omo-opencode label Jun 20, 2026
- Remove session.update call (not available in type definition)
- Fix callback type in auto-retry.ts to handle Promise<{ accepted: boolean }>
@EvangelosMoschou EvangelosMoschou changed the title fix(runtime-fallback): defer toast until dispatch succeeds, block restore of exhausted primary, persist fallback model fix(runtime-fallback): defer toast until dispatch succeeds, block restore of exhausted primary Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

opencode OpenCode edition: packages/omo-opencode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: runtime_fallback shows "Model Fallback" toast but primary session keeps retrying exhausted OpenAI model (OpenCode 1.17.5)

1 participant