isRetryableModelError() now checks the HTTP status code (429/503/529)
in addition to existing message pattern matching. This ensures rate
limit errors trigger model fallback regardless of error message format
or language (e.g., Chinese GLM errors).
Changes:
- ErrorInfo interface extended with statusCode?: number
- isRetryableModelError() checks statusCode after STOP patterns, before
message pattern fallback
- extractErrorStatusCode() added to error-classifier.ts (supports
statusCode, status, code, response.status fields)
- GLM-specific STOP patterns added: daily call limit, in arrears,
fair use policy, recharge and try — these prevent quota/billing 429s
from being treated as transient rate limits
- statusCode propagated through tryFallbackRetry and manager.ts
400 intentionally excluded from statusCode check (permanent client error).
Bare "403" and "forbidden" substring patterns (added in 034744cb to
retry the "Selected provider is forbidden" case from PR #3706) matched
any error message containing those tokens — tool-level 403s, file-perm
"forbidden" messages, unrelated upstream errors that happened to spell
"forbidden". The legacy model-fallback path then armed setPendingModelFallback
on those unrelated errors, jumping Sisyphus to claude-opus-4-7 (first
entry of its fallback chain) regardless of the user's configured model.
Replace the bare patterns with the specific phrases PR #3706 actually
targeted, and add regression tests asserting unrelated 403/forbidden
messages stay non-retryable.
Reported-by: ilove_borshch on Discord (#omo-help)
Bug fixes:
1. OAuth token refresh (#3149): buildHttpRequestInit() now attempts silent refresh
via refresh_token before triggering full browser re-auth. Added refresh() method
to McpOAuthProvider. Includes test isolation fix for discovery mock.
2. Quota error STOP (#3126): Added STOP_MESSAGE_PATTERNS in model-error-classifier
that take precedence over RETRYABLE_MESSAGE_PATTERNS. Message-only quota errors
now non-retryable. Runtime-fallback: quota_exceeded with 'retrying in' signal
still triggers fallback (provider-managed auto-retry). Restored removed patterns.
3. Compaction loop (#3127): MAX_RECOVERY_ATTEMPTS=3 cap + additional suppression
guard from opencode session in degradation monitor.
Also: refactored extractAutoRetrySignal to auto-retry-signal.ts, new regression
tests for quota classifier and compaction degradation monitor.
GitHub Copilot performs rolling model updates which sometimes return
'400 Bad Request' when a model is temporarily unavailable. This error
was not in the retryable message patterns, causing model fallback to
not trigger and users getting stuck.
Changes:
- Added 'bad request' to RETRYABLE_MESSAGE_PATTERNS in model-error-classifier.ts
- Added test cases for 'bad request' pattern matching
Fixes#3130
model_not_supported errors from providers (e.g. OpenAI returning
{"error": {"code": "model_not_supported"}}) were not recognized as
retryable. Subagents would silently fail with no response, hanging the
parent session.
Fix:
- Add "model_not_supported", "model not supported", "model is not
supported" to RETRYABLE_MESSAGE_PATTERNS in model-error-classifier.ts
- Add regex patterns to RETRYABLE_ERROR_PATTERNS in
runtime-fallback/constants.ts to match "model ... is ... not ...
supported" with flexible spacing
- Add regression test covering all three variations
Now model_not_supported errors trigger the normal fallback chain instead
of silent failure.