The max_tokens Default Your Provider Raised That Doubled Your Tail Response Length
Your incident timeline shows no deploys. Your code did not change. Your traffic mix did not change. Your prompts did not change. And yet your p99 output length doubled inside a week, your downstream rendering layer started clipping responses, and your output-token bill rose 38% on traffic that wasn't asking for longer answers. The change was real, the regression was measurable, and nothing in your version control system records it — because the value that moved was one your code never sent.
The provider raised an implicit default. The release notes filed it under "improved long-form behavior." The parameter in question was max_tokens, which your application has been omitting since day one because the documented default was generous and your outputs rarely came close. The default moved from 4096 to 8192 to accommodate longer reasoning in the provider's newer models. Your application got the new default whether you wanted it or not, because the absence of a parameter is itself a configuration choice — and the provider owns the right to change the value behind it.
This is the failure mode where a "no-op" release on the provider's side propagates through your system as a behavior change, a cost change, and a UX change all at once, and your team's only diagnostic signal is the bill arriving at the end of the month.
The Absence of a Parameter Is a Configuration
When your client sends a request without max_tokens, you are not opting out of the parameter. You are opting into whatever the provider has chosen as the current default, with no notification when that value moves. Your application's behavior is parameterized by every option you did not send, and the contract you have with the provider on each of those options is unilateral on their side.
This is not theoretical. OpenAI's chat completions API has historically truncated responses at ~512 tokens when max_tokens was omitted on some pathways, and at 4096 on others, depending on the model and endpoint family. Azure OpenAI defaults to 4096 for many GPT-4 class models unless explicitly configured, and the same model accessed through different SKUs returns different defaults. Claude's max_tokens is required for the messages API, but the output ceiling — the largest value your account is permitted to set — moves over time as new beta headers ship: Sonnet 4.6 grew from 64k to 300k via the output-300k-2026-03-24 beta. Newer reasoning models legitimately spend 20,000-40,000 thinking tokens before emitting 500 visible tokens, which shifted the recommended max_tokens setting upward by an order of magnitude for the same visible output.
Every one of those numbers is a configuration that lives outside your code. When the provider moves any of them, your application's worst-case behavior moves with it, and the only way you find out is by observing the downstream effect.
The unstated assumption behind omitting a parameter is that the default is stable. The provider has never promised that. The documentation describes the current default, not the contract value. Read it carefully and you will find phrasing like "currently defaults to" or "may be adjusted to optimize quality" — language that explicitly reserves the right to change. The team that built on top of the absence of the parameter built on top of a value that was always going to move.
How the Failure Composes
The 38% cost spike is the easy part to detect, because your finance dashboard catches it. The expensive failures are the ones that ride along quietly.
Your UI was sized for the old worst case. A response card had a max-height set to fit 1500 tokens of rendered Markdown, with overflow hidden. The new tail of 2500-3000 token responses gets clipped on screen, and your users see authoritative-looking outputs that end mid-sentence. Your support channel receives reports framed as hallucinations or formatting bugs, because nobody on the user side has a frame for "the model wrote more than the UI knows how to render." The trace shows the model did the right thing for the 2800 tokens it produced. The UI is the layer that lied.
Your latency budget assumed an output distribution. Streaming kept first-token latency low, but total render time scales with output length. The p99 latency for "an answer arrives in full" moved from 4.2 seconds to 7.8 seconds, and your SLO that was set against the old distribution started missing in production without any code change to account for. Your alerting threshold was tuned to the old shape of the curve.
Your downstream consumers assumed a length contract. The agent that takes the model's output and feeds it to a tool was tested against summaries that fit in a single tool call's argument budget. Some of the new longer outputs exceed that budget. Tool calls that used to succeed start failing on truncation, the agent retries, the retry encounters the same truncation, and the symptom at the top of the stack is a confused agent loop with no obvious upstream cause.
Each of these is a contract your application held with itself, denominated in output tokens, that the provider's default change silently broke. None of them are visible at the layer where the change happened. All of them are visible at the layer where the user lives.
Pin Every Parameter Your Code Touches
The defensive move is mechanical: send every call parameter explicitly, with the value pinned as a constant in your codebase, even when that value matches the documented default. The cost is one line of code per parameter. The benefit is that the default of record is your code's default, not the provider's, and any change to behavior is now a change to your repository that ships through your normal review process.
This applies to max_tokens, temperature, top_p, top_k, frequency_penalty, presence_penalty, stop, seed, system prompt format, response format, and any tool-related budget — the entire surface of options the API exposes. If the parameter has a default the provider can change, you set it. If the parameter has no obvious value for your route, you pick one and document why, rather than letting "absent" mean "whatever they decide today."
There is an instinct to argue that explicit defaults are noise: the value matches the documented default anyway, the line of code adds nothing. The instinct is wrong. The value matches the documented default today. The line of code is the only thing standing between your application and silent reparameterization tomorrow. The cost of typing max_tokens: 2048 is paid once. The cost of not typing it is paid every time the default moves, in incident response and bill reconciliation that come without warning.
- https://help.openai.com/en/articles/5072518-controlling-the-length-of-openai-model-responses
- https://platform.claude.com/docs/en/about-claude/models/overview
- https://platform.claude.com/docs/en/build-with-claude/extended-thinking
- https://www.vellum.ai/llm-parameters/max-tokens
- https://www.digitalocean.com/community/tutorials/model-silent-versioning-problem
- https://tokenmix.ai/blog/thinking-tokens-billing-trap-2026
- https://www.traceloop.com/blog/catching-silent-llm-degradation-how-an-llm-reliability-platform-addresses-model-and-data-drift
- https://arxiv.org/abs/2511.07585
- https://muxup.com/2025q2/recommended-llm-parameter-quick-reference
- https://glaforge.dev/posts/2024/09/23/some-good-practices-when-integrating-an-llm-in-your-application/
