fix(agents): classify account-restricted model 400s as model_not_found#104878
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 12, 2026, 1:30 AM ET / 05:30 UTC. Summary PR surface: Source +8, Tests +41. Total +49 across 3 files. Reproducibility: yes. Codex preserves the exact account-restriction response body, and current main's classifier demonstrably sends that unmatched HTTP 400 through the generic format-failure path. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Mantis proof suggestion Next step before merge
Security Review detailsBest possible solution: Merge the focused shared-classifier match with its positive and negative regression cases, preserving the existing typed failover, fallback, and user-reply layers. Do we have a high-confidence way to reproduce the issue? Yes. Codex preserves the exact account-restriction response body, and current main's classifier demonstrably sends that unmatched HTTP 400 through the generic format-failure path. Is this the best way to solve the issue? Yes. The shared failover message classifier is the narrowest established owner, and the PR reuses the existing typed fallback and user-message pipeline rather than duplicating channel-specific behavior. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f696430fd8b4. Label changesLabel justifications:
Evidence reviewedPR surface: Source +8, Tests +41. Total +49 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
052b45f to
6933e9d
Compare
OpenAI-backed runtimes reject plan/account-restricted models with HTTP
400 invalid_request_error ("The '<model>' model is not supported when
using Codex with a ChatGPT account."). Without a model_not_found match
the ambiguous-400 branch collapses this into a format failure, so users
get the generic retry//new copy for a config-only problem (openclaw#104490).
The 'when using' qualifier keeps capability rejections ("not supported
for tool calling") out of the class, preserving the openclaw#97611 contract.
Fixes openclaw#104490
6933e9d to
7abbb6f
Compare
|
Live verification refreshed after the maintainer review and preparation pass on exact PR head The review found one important false-positive boundary in the original pattern: Personal Cloudflare Crabbox proof on the byte-identical prepared tree:
The guarded push verified that the remote PR head tree exactly matches the Cloudflare-validated local tree, and the PR is mergeable against current Hosted-check note: 65 checks have passed. Two non-required shards failed outside this PR's three-file agent-classifier scope: the plugin-SDK surface budget observed 210 wildcard re-exports against its pinned 209 baseline, and an unrelated TUI PTY E2E timed out after its mock provider response returned. Four platform scans remain queued/running. I did not blindly rerun the deterministic plugin-SDK failure. Review finding resolved and preparation complete. |
|
Merged via squash.
|
openclaw#104878) * fix(agents): classify account-restricted model 400s as model_not_found OpenAI-backed runtimes reject plan/account-restricted models with HTTP 400 invalid_request_error ("The '<model>' model is not supported when using Codex with a ChatGPT account."). Without a model_not_found match the ambiguous-400 branch collapses this into a format failure, so users get the generic retry//new copy for a config-only problem (openclaw#104490). The 'when using' qualifier keeps capability rejections ("not supported for tool calling") out of the class, preserving the openclaw#97611 contract. Fixes openclaw#104490 * fix(agents): narrow account model rejection match --------- Co-authored-by: Altay <[email protected]>
openclaw#104878) * fix(agents): classify account-restricted model 400s as model_not_found OpenAI-backed runtimes reject plan/account-restricted models with HTTP 400 invalid_request_error ("The '<model>' model is not supported when using Codex with a ChatGPT account."). Without a model_not_found match the ambiguous-400 branch collapses this into a format failure, so users get the generic retry//new copy for a config-only problem (openclaw#104490). The 'when using' qualifier keeps capability rejections ("not supported for tool calling") out of the class, preserving the openclaw#97611 contract. Fixes openclaw#104490 * fix(agents): narrow account model rejection match --------- Co-authored-by: Altay <[email protected]>
What Problem This Solves
When an embedded Codex run fails because the configured model is not available on the account/plan (HTTP 400
invalid_request_error: "The 'gpt-5.5-pro' model is not supported when using Codex with a ChatGPT account."), Telegram users get the generic "Something went wrong… try again, or use /new" copy (#104490). Retrying or/newcannot fix a model/account mismatch — the actionable cause was only visible in gateway logs.Why This Change Was Made
Root cause is a classification gap, not missing copy. The pipeline for this failure already exists end to end:
classifyFailoverSignal(src/agents/embedded-agent-helpers/errors.ts) explicitly inspects the payload first "so … model-not-found errors … are not collapsed into generic format failures" — butisModelNotFoundErrorMessage(src/agents/live-model-errors.ts) had no pattern for the account-restricted rejection sentence, so the error fell through toformat.model_not_foundreason, the assistant-failover layer already does the right things: tries configured fallback models, and when surfacing throws a typedFailoverError.classifyProviderRequestErroralready maps that typed reason toPROVIDER_MODEL_UNAVAILABLE_USER_MESSAGE— copy that matches the issue's expectation ("needs a config update…; retrying or starting a new session won't fix it").The fix is one pattern in the classifier that owns model-error text:
model … is not supported when using …. The "when using" qualifier keeps capability rejections ("This model is not supported for tool calling.") out of the class — the existing negative tests still pass. No change toclassifyProviderRequestError: its "typed reason only, no text matching" contract from #97611 is preserved (the message-level classification happens in the failover layer that owns it).The issue's admin-notification request (route sanitized diagnostics to an operator) is a separate product surface and intentionally not part of this fix.
User Impact
format-classified error.Evidence
Executed real-module before/after (tsx over the actual source chain — failover classification → surfaced
FailoverError→ user-copy classification — using the exact payload from the issue's logs):Before (main):
After (this PR):
Both runs include the capability-rejection guard:
"This model is not supported for tool calling."stays out ofmodel_not_foundbefore and after.Regression tests (
node scripts/run-vitest.mjs run src/agents/live-model-errors.test.ts src/agents/failover-error.test.ts src/auto-reply/reply/provider-request-error-classifier.test.ts→ 122/122):live-model-errors.test.ts: the issue sentence and its raw JSON envelope both classify as model-not-found; existing negatives (capability/feature rejections) unchanged.failover-error.test.ts:status: 400+ the Codex payload resolves tomodel_not_found; capability 400s do not.live-model-errors.tsfails both new tests (2 failed | 97 passed), confirming they gate the fix.Codex-side provenance: the sibling
openai/codexcheckout confirms this rejection is generated by the OpenAI backend and forwarded verbatim (codex-rs/core/tests/suite/compact.rsmocks it as an HTTP 400invalid_request_error;codex-rs/protocol/src/error.rsUnexpectedResponseErrorcarries the body through), so message-level classification on the OpenClaw side is the right seam.Fixes #104490