Skip to content

Commit 7abbb6f

Browse files
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 (#104490). The 'when using' qualifier keeps capability rejections ("not supported for tool calling") out of the class, preserving the #97611 contract. Fixes #104490
1 parent 66cd7b1 commit 7abbb6f

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

src/agents/failover-error.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,35 @@ describe("failover-error", () => {
377377
).toBe("model_not_found");
378378
});
379379

380+
it("classifies account-restricted model 400s as model_not_found (#104490)", () => {
381+
// Codex/OpenAI reject plan-restricted models with HTTP 400
382+
// invalid_request_error; without a model_not_found classification the 400
383+
// branch collapses this into "format" and users get generic retry//new copy
384+
// for a config-only failure.
385+
const codexAccountRestrictedPayload =
386+
'{"type":"error","status":400,"error":{"type":"invalid_request_error","message":"The \'gpt-5.5-pro\' model is not supported when using Codex with a ChatGPT account."}}';
387+
expect(
388+
resolveFailoverReasonFromError({
389+
provider: "codex",
390+
status: 400,
391+
message: codexAccountRestrictedPayload,
392+
}),
393+
).toBe("model_not_found");
394+
expect(
395+
resolveFailoverReasonFromError({
396+
message:
397+
"The 'gpt-5.5-pro' model is not supported when using Codex with a ChatGPT account.",
398+
}),
399+
).toBe("model_not_found");
400+
// Capability rejections stay out of the model_not_found class.
401+
expect(
402+
resolveFailoverReasonFromError({
403+
status: 400,
404+
message: "This model is not supported for tool calling.",
405+
}),
406+
).not.toBe("model_not_found");
407+
});
408+
380409
it("does not classify generic access errors as model_not_found", () => {
381410
expect(
382411
resolveFailoverReasonFromError({

src/agents/live-model-errors.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ describe("live model error helpers", () => {
4141
),
4242
).toBe(true);
4343
expect(isModelNotFoundErrorMessage("Not supported model some-model-id")).toBe(true);
44+
// #104490: account/plan-restricted model rejections (Codex + ChatGPT
45+
// account) are model-unavailable, including the raw JSON envelope shape.
46+
expect(
47+
isModelNotFoundErrorMessage(
48+
"The 'gpt-5.5-pro' model is not supported when using Codex with a ChatGPT account.",
49+
),
50+
).toBe(true);
51+
expect(
52+
isModelNotFoundErrorMessage(
53+
'{"type":"error","status":400,"error":{"type":"invalid_request_error","message":"The \'gpt-5.5-pro\' model is not supported when using Codex with a ChatGPT account."}}',
54+
),
55+
).toBe(true);
4456
expect(
4557
isModelNotFoundErrorMessage(
4658
"404 The free model has been deprecated. Transition to qwen/qwen3.6-plus for continued paid access.",

src/agents/live-model-errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export function isModelNotFoundErrorMessage(raw: string): boolean {
3131
if (/\bnot supported model\b/i.test(msg)) {
3232
return true;
3333
}
34+
// OpenAI-backed runtimes reject account/plan-restricted models with
35+
// "The '<model>' model is not supported when using <runtime> with <account>".
36+
// The model id must change (or the account), so treat it as model-unavailable;
37+
// the "when using" qualifier keeps capability errors ("not supported for
38+
// tool calling") out of this class.
39+
if (/\bmodel\b[^.]{0,120}?\bis not supported when using\b/i.test(msg)) {
40+
return true;
41+
}
3442
if (/model:\s*[a-z0-9._/-]+/i.test(msg) && /not(?:[_\-\s])?found/i.test(msg)) {
3543
return true;
3644
}

0 commit comments

Comments
 (0)