Description
In v2 mode, the v2/client-shim.ts makes both session.prompt and session.promptAsync no-ops. This silently breaks the ForegroundFallbackManager.execFallback mechanism, which relies on promptAsync to replay the user message with the fallback model.
Impact
When a provider hits a rate limit (e.g., OpenAI quota exceeded), the fallback chain is configured correctly, isFailoverError matches, shouldTriggerFallback returns true, and execFallback calculates the correct next model — but the actual model switch never happens because promptAsync is ignored.
The user sees the error repeated N times (where N = maxRetries) with no model switch, even though fallback chains are properly configured in presets.
Root Cause
src/v2/client-shim.ts (line ~45940 in dist):
promptAsync: async (args) => {
log("[v2][shim] session.promptAsync ignored", { id: args?.sessionID });
return {};
}
The ForegroundFallbackManager.execFallback (line ~27448) calls:
await sessionClient.promptAsync(promptBody);
This silently succeeds but does nothing. The manager logs "switched to fallback model" but no prompt is actually sent.
Reproduction
- Configure a preset with a fallback chain (e.g.,
openai/gpt-5.6-terra -> opencode-go/mimo-v2.5)
- Exhaust the primary provider quota
- Observe: error repeats, no model switch occurs,
promptAsync no-op logs appear
Local Workaround
Patch the v2 shim to forward promptAsync via the HTTP API:
promptAsync: async (args) => {
const id = args?.path?.id ?? args?.sessionID;
log("[v2][shim] session.promptAsync via HTTP fallback", { id });
try {
const serverUrl = "http://localhost:4096";
const resp = await fetch(`${serverUrl}/session/${id}/prompt`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args?.body ?? args)
});
return resp.ok ? {} : { error: resp.statusText };
} catch (err) {
log("[v2][shim] promptAsync HTTP failed", { error: err instanceof Error ? err.message : String(err) });
return {};
}
}
Suggested Fix
Two options:
-
Short term: Forward promptAsync to the OpenCode HTTP API (as shown in the workaround above) instead of no-op'ing it. The execFallback call path would work as-is.
-
Long term: Expose a dedicated model-switch API in v2 (e.g., session.setModel(sessionId, modelRef)) so the fallback manager can switch models without replaying the entire message. This avoids the semantic mismatch of re-sending user messages for what is essentially a model routing change.
Related
Environment
- oh-my-opencode-slim: v2.2.17
- OpenCode: v2 runtime
- Auth plugin: custom multi-account auth rotation plugin (not directly related but compounds the issue — rotation absorbs 429s until all accounts are exhausted, then the broken fallback fails to switch models)
Description
In v2 mode, the
v2/client-shim.tsmakes bothsession.promptandsession.promptAsyncno-ops. This silently breaks theForegroundFallbackManager.execFallbackmechanism, which relies onpromptAsyncto replay the user message with the fallback model.Impact
When a provider hits a rate limit (e.g., OpenAI quota exceeded), the fallback chain is configured correctly,
isFailoverErrormatches,shouldTriggerFallbackreturns true, andexecFallbackcalculates the correct next model — but the actual model switch never happens becausepromptAsyncis ignored.The user sees the error repeated N times (where N =
maxRetries) with no model switch, even though fallback chains are properly configured in presets.Root Cause
src/v2/client-shim.ts(line ~45940 in dist):The
ForegroundFallbackManager.execFallback(line ~27448) calls:This silently succeeds but does nothing. The manager logs "switched to fallback model" but no prompt is actually sent.
Reproduction
openai/gpt-5.6-terra->opencode-go/mimo-v2.5)promptAsyncno-op logs appearLocal Workaround
Patch the v2 shim to forward
promptAsyncvia the HTTP API:Suggested Fix
Two options:
Short term: Forward
promptAsyncto the OpenCode HTTP API (as shown in the workaround above) instead of no-op'ing it. The execFallback call path would work as-is.Long term: Expose a dedicated model-switch API in v2 (e.g.,
session.setModel(sessionId, modelRef)) so the fallback manager can switch models without replaying the entire message. This avoids the semantic mismatch of re-sending user messages for what is essentially a model routing change.Related
Environment