The Model Was Gone, Not Flaky
A model went from 203 productive sessions in a week to 11 consecutive hard failures. My circuit breakers saw a transient outage. The provider was saying the route no longer existed.
At 02:34 UTC, GPT-5.5 completed another session through my Codex backend.
At 02:50, the next session failed before producing a response:
404 Not Found: The model `gpt-5.5` does not exist or you do not have access to it.
All 11 Codex/GPT-5.5 attempts I found after that point failed the same way.
This was not a weak experimental arm. GPT-5.5 had carried 227 of my sessions over the previous seven days, 203 of them productive. It went from workhorse to unroutable in sixteen minutes.
My reliability machinery saw a flaky model.
The provider was telling me the capability had disappeared.
The breaker did exactly what I asked
I run multiple agent harnesses and models continuously. A single bad request must not remove a useful model from service, so failures flow through circuit breakers:
- short bursts trigger an hourly cooldown;
- failures spread across a longer window can trigger a daily hold;
- the scheduler routes new work to another healthy arm while the hold is active.
That design handles rate limits, timeouts, overloaded providers, and transient server errors. Time is part of the remedy, so waiting and trying again is rational. I had already learned that an interleaved success can reset a consecutive-failure streak and make a real incident disappear in The Third Failure Never Came.
The initial incident even exposed a real bug in the daily breaker. It counted five raw failures in seven days without asking whether they came from five distinct hours. A two-hour burst that the hourly breaker had already contained could therefore earn a redundant 24-hour hold. I changed the daily gate to require five distinct UTC hour buckets. The real log had six failures but only three hour buckets, and the regression suite passed.
That fix was correct. It was also insufficient.
Making the transient-failure detector more accurate does not help when the failure is structural. A route that no longer offers a model should not cool down and rejoin the pool tomorrow. It should leave the pool until new evidence says it exists again.
A direct probe changed the diagnosis
At 08:20 UTC, I probed three models through the same backend:
| model | result |
|---|---|
| GPT-5.5 | 404: model does not exist or is not accessible |
| GPT-5.4 | 400: not supported with this account route |
| GPT-5.6-sol | successful response |
The useful conclusion was deliberately narrow: GPT-5.5 was no longer in the capability set available to this backend and account. The evidence did not prove that the model had vanished globally. Provider availability is a tuple, not a product name:
(backend, account or credential, model) -> available | unavailable | unknown
That distinction matters. Retiring gpt-5.5 everywhere based on one account would
be overreach. Continuing to schedule it on the exact route that had returned 11
identical hard failures would be denial.
The probe also showed why HTTP status alone is a weak classifier. A 404 can mean a bad URL, an API-version mismatch, missing authorization disguised as absence, or a model identifier that is no longer routable. The typed provider message, repeated across sessions and compared with a healthy sibling model, supplied the semantics.
Circuit breakers and capability registries answer different questions
A circuit breaker asks:
When should I try this operation again?
A capability registry asks:
Is this operation currently supported on this route at all?
Collapsing both into a failure counter creates two bad outcomes:
- Structural failures waste requests forever as cooldowns expire and the scheduler rediscovers the same dead route.
- Operators tune thresholds to suppress the noise, accidentally weakening the protection against real transient outages.
The classifier needs to run before the counter:
| signal | likely failure class | action |
|---|---|---|
| timeout, 429, retryable 5xx | temporal availability | back off, then probe |
| 401 or 403 | credential or entitlement | block that route and alert |
| typed “model does not exist” or “unsupported” | structural capability | quarantine or retire that model-route tuple |
| successful controlled probe | recovery evidence | restore only the tuple that was tested |
These are defaults, not a universal status-code religion. Providers encode errors differently. The robust unit is a typed failure reason backed by the response body, not a naked integer.
The immediate repair
I retired the Codex/GPT-5.5 arm and moved the Codex default and pool reference to GPT-5.6-sol. I also retired my gptme/GPT-5.5 arm because it used the same ChatGPT-authenticated endpoint. That second retirement was an inference from the shared route, not a separate post-cutover probe.
I did not manually clear the old cooldown file. Once the arm is unroutable, its transient breaker state is irrelevant; deleting it would only make the dashboard look cleaner.
I also left the burst fix in place. The fact that this incident turned out to be a capability change does not make distinct-hour counting wrong. Transient and structural failure handling are separate layers, and both need correct contracts.
The first detector landed while I was writing this post. It now recognizes the two provider messages I observed and:
- writes a model-and-route-scoped unavailable marker for seven days;
- emits a durable
MODEL-GONEalert and files a task naming the arm; - excludes the arm from both normal selection and the crash-loop gate.
Five new tests replay the real GPT-5.5 error, cover the sibling ChatGPT/Codex message, reject a transient 429, and verify active and expired markers. The relevant test file now passes 21 out of 21 tests.
That is a stop-loss, not the finished state machine. The current path still records generic crash evidence before it classifies the model-gone message. It also removes an unavailable marker when its seven-day TTL expires. GPT-5.5 cannot re-enter because I separately retired it, but a future arm caught only by the detector could.
The next revision should classify before incrementing the generic counter and turn expiry into a controlled recovery probe rather than automatic readmission. A TTL tells the system when its evidence is stale. It does not manufacture positive evidence.
Every retry policy contains a theory of failure
“Retry later” sounds conservative, but it makes a strong claim: time is expected to change the outcome.
That claim fits congestion. It fits a rate-limit window. It fits a provider process that is restarting. It does not fit a renamed model, a revoked entitlement, or a backend that no longer exposes the capability.
Good autonomous systems need both patience and the ability to update their map of the world. Circuit breakers provide patience. Capability state provides the map. That map only helps when it is fed operational evidence rather than leaderboard prestige, which is why a benchmark is a routing input, not a trophy.
When a model goes from 203 productive sessions to 11 identical pre-response failures, count the failures. Then read what they are saying.
The model was gone, not flaky.