Skip to content

Commit a436ebf

Browse files
RomneyDaclaude
andcommitted
fix(reply): show a clear "model unavailable" reply instead of generic failure
When a configured model is retired/renamed by the provider, model resolution fails (run.ts throws a typed FailoverError with reason "model_not_found") and the agent runner falls back to the generic "Something went wrong … use /new" reply. That copy is actively misleading for this failure: retrying or starting a new session can never help, because the model id itself must be changed in config. Users on a deprecated model (e.g. gpt-5.3-codex on a Codex ChatGPT account) just see the generic message on every message and on /new. Classify this failure into a dedicated user-facing reply that explains the model is unavailable and points at the config. Detection is structural — it keys off the typed FailoverError reason, not provider error text — so it stays robust as provider wording changes; free-text rejections without a typed reason remain the responsibility of the failover layer that owns error classification. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1 parent 586912a commit a436ebf

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/auto-reply/reply/provider-request-error-classifier.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,
77
PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE,
88
PROVIDER_INTERNAL_ERROR_USER_MESSAGE,
9+
PROVIDER_MODEL_UNAVAILABLE_USER_MESSAGE,
910
PROVIDER_RATE_LIMIT_OR_QUOTA_ERROR_USER_MESSAGE,
1011
} from "./provider-request-error-classifier.js";
1112

@@ -55,6 +56,41 @@ describe("provider request error classifier", () => {
5556
).toBeUndefined();
5657
});
5758

59+
it("classifies typed model_not_found failover errors as model unavailable", () => {
60+
const error = new FailoverError(
61+
'Unknown model: openai/gpt-5.3-codex. Found agents.defaults.models["openai/gpt-5.3-codex"] bound to the "codex" agent runtime.',
62+
{
63+
reason: "model_not_found",
64+
provider: "openai",
65+
model: "gpt-5.3-codex",
66+
},
67+
);
68+
69+
expect(classifyProviderRequestError(error)).toEqual({
70+
code: "provider_model_unavailable",
71+
userMessage: PROVIDER_MODEL_UNAVAILABLE_USER_MESSAGE,
72+
technicalMessage: error.message,
73+
});
74+
});
75+
76+
it("does not classify model-not-found from raw provider text alone", () => {
77+
// Detection is structural (typed failover reason), not text matching: a
78+
// bare error string without the typed reason is left unclassified.
79+
expect(
80+
classifyProviderRequestError(new Error("Unknown model: openai/gpt-5.3-codex")),
81+
).toBeUndefined();
82+
});
83+
84+
it("does not misclassify other typed failover reasons as model unavailable", () => {
85+
const error = new FailoverError("Provider overloaded.", {
86+
reason: "overloaded",
87+
provider: "openai",
88+
model: "gpt-5.5",
89+
});
90+
91+
expect(classifyProviderRequestError(error)).toBeUndefined();
92+
});
93+
5894
it.each([
5995
[
6096
"OpenAI missing custom tool output",

src/auto-reply/reply/provider-request-error-classifier.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type ProviderRequestErrorCode =
1212
| "provider_authentication_error"
1313
| "provider_conversation_state_error"
1414
| "provider_internal_error"
15+
| "provider_model_unavailable"
1516
| "provider_rate_limit_or_quota_error";
1617

1718
/** Structured provider error classification for reply failure handling. */
@@ -33,6 +34,14 @@ export const PROVIDER_INTERNAL_ERROR_USER_MESSAGE =
3334

3435
export const PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE = `⚠️ ${AUTH_INVALID_TOKEN_USER_TEXT}`;
3536

37+
/**
38+
* User-facing copy for a configured model the provider no longer serves.
39+
* Distinct from generic failures because retrying or starting a new session
40+
* cannot help: the model id itself must be changed in config.
41+
*/
42+
export const PROVIDER_MODEL_UNAVAILABLE_USER_MESSAGE =
43+
"⚠️ The configured model is unavailable from the provider — it may have been renamed, retired, or is not offered on this account. This needs a config update (agents.defaults.model); retrying or starting a new session won't fix it.";
44+
3645
/** Classifies provider request failures that are actionable for users. */
3746
export function classifyProviderRequestError(
3847
err: unknown,
@@ -49,6 +58,17 @@ export function classifyProviderRequestError(
4958
technicalMessage,
5059
};
5160
}
61+
// Detect retired/unavailable models structurally via the typed failover
62+
// reason set at resolution time (run.ts), not by matching provider error
63+
// text. Free-text provider rejections without a typed reason are left to the
64+
// failover layer that owns error classification.
65+
if (isFailoverError(err) && err.reason === "model_not_found") {
66+
return {
67+
code: "provider_model_unavailable",
68+
userMessage: PROVIDER_MODEL_UNAVAILABLE_USER_MESSAGE,
69+
technicalMessage,
70+
};
71+
}
5272
if (
5373
hasHttp429Evidence(err, technicalMessage) &&
5474
isGenericProviderRuntimeErrorMessage(technicalMessage)

0 commit comments

Comments
 (0)