OpenCode plugin that, on a rate-limit error, automatically switches to a fallback model and resends a new retry message — without wiping the session.
⚠️ This is not theopencode-loopplugin (auto-continue/loop). This is a separate rate-limit fallback plugin.
This project is a fork of
opencode-rate-limit-fallback
by liamjv1 (v0.3.1, MIT). It fixes two serious defects in the upstream plugin —
"it never triggers" and "it erases the user's work" — and refactors/de-duplicates the
code.
| Behavior | Original (opencode-rate-limit-fallback) |
This fork (opencode-retry) |
|---|---|---|
| Event listened to | only session.status with retry status |
session.error and session.status/retry |
Reacts to Bad Request rate-limit error |
No — that error is emitted as session.error, which was never handled, so the plugin never fired |
Yes — session.error triggers the fallback |
| On trigger | abort → revert to last user message → re-send the same prompt |
send a new configurable message (retryMessage) on the fallback model |
| Session history | Wiped — assistant replies are deleted from the log by abort/revert |
Preserved — only session.prompt is called, nothing is reverted |
| Retry cadence | passive — waits for the next error event | active — schedules a retry after the cooldown |
| Default fallback model | anthropic/claude-opus-4-5 |
same default (configurable) |
| Retry message text | re-sends the original prompt | configurable retryMessage, default "go" |
| Area | Original | This fork |
|---|---|---|
fallbackModel |
string only | string or { providerID, modelID } object (parseModel) |
| Event handling | single session.status branch |
three branches: session.status, session.error, session.deleted (cleanup) |
| Destructive calls | session.abort + session.revert |
none — only session.prompt |
| Logic structure | all inline in the event handler | extracted helpers: createPatternMatcher, isCoolingDown, sendFallbackPrompt, scheduleRetryAfterCooldown, triggerFallback |
| Cooldown check | duplicated in two places | single helper isCoolingDown used by triggerFallback |
retryCount state |
present but never used to limit retries (dead code) | removed |
| Config lookup | root config dir only | root plus config, plugins, plugin subdirs; Windows %APPDATA% support |
| Package name / version | opencode-rate-limit-fallback 0.3.1 |
opencode-retry 1.0.0 |
When your model provider enforces a request rate limit (e.g. "you exceeded the limit of 5 requests in 5 seconds"), opencode surfaces an error like:
AI_APICallError: Bad Request: {"detail":"You exceeded the limit of 5 requests in 5 seconds..."}
The original upstream plugin only listened to the session.status event with a
retry status, but in recent opencode versions this error is emitted as a
session.error event — so the original handler never fired.
Worse, the original logic did abort → revert to the last user message and
re-sent the same prompt. That deleted assistant replies from the visible session
log, effectively "erasing the user's work."
- Listens for both
session.errorandsession.status/retryevents. - Matches the error text against configurable
patterns. - On a match, sends a new message (configurable
retryMessage, default"go") on the fallback model — a singlesession.promptcall. - No
abort/revert: the session history (assistant replies) is preserved. - Schedules an active retry after the cooldown so it keeps retrying instead of passively waiting for the next error event.
Add the plugin to ~/.config/opencode/opencode.jsonc:
Create ~/.config/opencode/rate-limit-fallback.json:
{
"enabled": true,
"fallbackModel": "openwebui/Deepseek/DeepSeek-V4-Flash-0731",
"retryMessage": "go",
"cooldownMs": 30000,
"patterns": ["Bad Request"],
"logging": true
}| Key | Default | Description |
|---|---|---|
enabled |
true |
Master switch. |
fallbackModel |
anthropic/claude-opus-4-5 |
Model to switch to on rate limit. |
retryMessage |
"go" |
Text of the new message sent as the retry. |
cooldownMs |
300000 |
Pause between retries. |
patterns |
["rate limit", "usage limit", "too many requests", "quota exceeded", "overloaded"] |
Substrings of the error text that identify a rate limit. |
logging |
false |
Write an activity log. |
The config file is looked up in ~/.config/opencode/ and its config, plugins,
and plugin subdirectories (on Windows, %APPDATA%\opencode).
fallbackModel may be given either as a string ("provider/model") or as an object
({ "providerID": "...", "modelID": "..." }).
When logging: true, an activity log is written to
~/.local/share/opencode/logs/rate-limit-fallback.log.
Expected lines of the fixed (non-destructive) version:
[INFO] Plugin initialized ...
[INFO] Failure detected, switching to fallback ...
[INFO] Sending new prompt on fallback model ...
[INFO] Fallback prompt sent successfully ...
There should be no Aborting / Reverting lines — those belonged to the old
destructive version.
- Plugin seems stale / still erases work. The plugin is loaded when the opencode
process starts. If opencode is already running, an old copy may remain in
memory (log still shows
Aborting/Reverting). Restart opencode so the new non-destructive version takes effect. - Not retrying / not firing after cooldown. After restarting, confirm the log no
longer shows
Aborting/Reverting, then checkcooldownMsandretryMessage. - Rare errors. Increase
cooldownMs(e.g. to60000) to hit the API less often.
npm install
npm run typecheck
{ "plugin": ["opencode-retry"] }