The report was that one feature had started failing, but only on the highest quality setting. Everything else worked. No commit had touched that path in days. Users saw a generic error, which is the worst possible version of a bug because it tells you nothing and tells them less.
Finding it
My first instinct was an account or quota problem, since the failure tracked a paid tier. That was wrong, and proving it wrong was the useful step: I wrote a five-line script that made the simplest possible call with the same credentials. It succeeded. Then I added one parameter. It failed.
That was the whole answer. A newer model had stopped accepting a sampling parameter that older models accept. My code passed that parameter unconditionally, because I wanted reproducible output. So the failure split perfectly along model lines, which is exactly why it looked like a tier or billing problem instead of a request problem.
Three things I changed
First, the parameter now goes through an allowlist of models known to accept it. An unrecognised model gets the parameter omitted rather than sent. That is deliberate: losing a little reproducibility is cheap, and sending an unsupported parameter breaks the entire feature. When you are guessing, guess in the direction that degrades.
Second, that client no longer crashes when a parameter it expects has vanished from the library. It notices and continues without it. A missing optional parameter should cost quality, never availability.
Third, the dependency got an upper version bound, with a comment next to it explaining what the absence of that bound had already cost.
# The ceiling is deliberate, and it was paid for. Without it, a rebuild
# resolved a newer SDK that had dropped a parameter this code passes, and
# every request died before it was even sent. Raise it on purpose, with the
# live path exercised against a real key — not as a routine bump.
"provider-sdk>=0.104.1,<0.105.0",Why the comment matters more than the pin
A bare version ceiling reads like paranoia. Six months later someone — possibly me — widens it during a tidy-up, because there is no visible reason for it to be there, and the same outage happens again with no memory of the first one.
A ceiling with the incident written beside it is a different object. It carries its own justification, and it makes removing it a decision rather than housekeeping.
The test I should have had first
There is now a table-driven test asserting, per model, whether the parameter is sent — including the case of a model the allowlist has never seen. That last case is the one that matters, because it pins the fail-safe behaviour rather than the happy path.
Your dependencies can change behaviour without your git log showing anything at all. Pin the ones that talk to the outside world, and say why.