ForegroundFallbackManager (src/hooks/foreground-fallback/index.ts) detects rate-limit
errors from OpenCode plugin events (session.error, session.status, message.updated)
and switches to the next model in the agent's configured fallback chain.
The OpenCode proxy returns "Monthly usage limit reached" for all models going through it. The fallback should catch this and switch models, but on a fresh session it doesn't trigger.
Two hypotheses:
Also: subagent sessions (explorer, librarian, fixer) show zero
[foreground-fallback] log entries — their errors don't reach the handler either.
Branch: fix/foreground-fallback-diagnostic
Minimal diagnostic logging added. No behavioral changes.
extractErrorPreview() — extracts an error string from any event shape:
session.error → properties.errormessage.updated → properties.info.errorsession.status → properties.status.messagehandleEvent() diagnostic — logs EVERY event reaching the handler BEFORE the
switch statement with { type, sessionID, error }. This is the key: it captures
events that fall through unhandled.
tryFallback() diagnostic — logs entry with { sessionID, inProgress, dedupMs }
BEFORE any early-return guards, so we can tell if fallback is entered vs filtered.
The stale dist was missing these source changes — the fresh build includes them:
isRateLimitError() for
broader detectionisUserMessageWithParts() guard: Prevents crash when messages have undefined
info (OpenCode sometimes returns partial/streaming messages)currentModel inference: Infers current model as chain[0] when agent
name is known but no model was captured yetbun run buildopencode.jsonc plugin to local path)Plugin log location: ~/.local/share/opencode/log/oh-my-opencode-slim.*.log
| Log pattern | What it means |
|---|---|
[foreground-fallback] event { type: "...", sessionID: "...", error: "..." } |
Event arrived at the handler. Shows event type, session ID, and any error message. |
[foreground-fallback] tryFallback { sessionID: "...", inProgress: false, ... } |
Fallback procedure was entered (rate-limit signal was recognized). |
[foreground-fallback] resetting tried set for re-fallback |
Chain was exhausted, resetting to try last model again (recovery kicking in). |
[foreground-fallback] switched to fallback model { from: "...", to: "..." } |
Fallback worked — model was switched. |
No event log at all when monthly limit fires |
Hypothesis A confirmed — plugin never sees the error |
event log appears but NO tryFallback |
Event hits a handler gap (e.g., wrong status type, missing sessionID). The event log shows which type — may need to add a new case. |
event + tryFallback but no switched |
Some downstream failure — subsequent logs will say why (no user message, promptAsync unavailable, invalid model, etc.) |
Paste the relevant log lines back and we'll identify the exact breakdown point.