If you're running LLMs in production, this one dependency has no version management story: the one generating your outputs.
Every software dependency you use gives you a version number. That version number comes with a changelog, a lockfile entry, and a diff you didn't have to ask for. If a library update breaks something, you know immediately and you roll back. Model providers give you a version string you can pin, but no changelog, no automatic diff, and no notification when behavior changes. When OpenAI ships gpt-5.5 or Anthropic releases Claude Sonnet 5, you find out when something downstream breaks. Or you don't find out at all.
In a previous Field Notes piece, I built a governance layer that measures whether AI security reviews are consistent enough to trust. One of the design decisions was treating model selection as a governance variable: switching models changes what gets found. I wanted to explore the follow-up question here. What happens when you don't switch models, but the model switches on you?
Not all output changes are equal
When a new model version ships, you need a way to measure what changed. I created a capture script that runs nine prompts against a specified version and saves everything: raw output, token counts, latency, and automated signals. Each run goes to a timestamped directory with a manifest and one text file per prompt, diff-ready against any previous run.
Structural prompts are three Terraform generation tasks: a VPC with public and private subnets, a least-privilege IAM role, and an S3 module with variable validation. These produce deterministic infrastructure code. Automated signals (resource count, variable count, output count, line count) tell you whether the structural output shifted. Human scoring is still required for correctness, but the automated signals do the heavy lifting.
Judgment prompts are six DevOps decision tasks: a Terraform security review, an infrastructure diff review, a 2am incident triage, an architecture tradeoff recommendation, a CrashLoopBackOff diagnosis, and a Python code review. These require the model to make decisions, assign severity, prioritize, and recommend. Automated signals are weak here. The data that matters is the verdict, the severity ranking, and whether a real engineer would have done something different based on the output. That last dimension, "Decision Changed," is defined strictly: different wording is not a decision change, a missing recommendation section is.
A separate canary runs the same suite on a weekly schedule. When it flags a version transition, that's the trigger to run a full capture against the new version and compare.
Two categories of failure
Looking only at the Terraform outputs, you'd conclude nothing changed. The VPC came out with 14 resource blocks in every version across both providers, correctness scores were 4/5 across the board, and nothing flagged. The judgment tier is where the drift lives. Let's dig into the data to see why.
I ran the suite against four models: GPT-5.4, GPT-5.5, Claude Sonnet 4.6, and Claude Sonnet 5. Each provider had a version update during the test period. Two categories of failure came out of it.
Finding 1: The judgment tier drifted
Severity calibration, actionability, and verdict completeness all shifted across version updates. Correctness scores held through each run, but the drift showed up in the things that mattered for decisions.
Severity calibration. GPT-5.4 caps at High across all three review prompts. GPT-5.5 introduced Critical on one of them. Both Sonnet versions use Critical across all three. The calibration shifted partially, which is harder to handle than a clean break. A downstream system routing on severity now behaves differently depending on which version it's running against, and nothing in your codebase changed.
| Prompt | GPT-5.4 | GPT-5.5 | Sonnet 4.6 | Sonnet 5 |
|---|---|---|---|---|
| tf_security_review (highest severity used) | High | High | Critical | Critical |
| devops_code_review (highest severity used) | High | High | Critical | Critical |
| devops_diff_review (highest severity used) | High | Critical | Critical | Critical |
Actionability. This was the finding I would not have noticed without running the comparison.
On the incident triage prompt, all four models correctly diagnosed database connection pool exhaustion. Correctness scores were 5/5 across the board. But the outputs diverged on what you could actually do with them. GPT-5.4 provided exact SQL queries for connection state inspection, a complete incident channel message template, and step-by-step diagnostic commands. GPT-5.5 diagnosed correctly but delivered less: no SQL, no incident command structure, fewer concrete next steps. Sonnet showed the same pattern. Sonnet 4.6 included SQL, a full incident channel template with rationale for each element, and a diagnostic sequence. Sonnet 5 provided a correct diagnosis with stronger reasoning, but gave you fewer tools to act on.
Same correctness. Different utility. A quality check that only measures "did the model get the right answer" would score these identically. An engineer at 2am would not experience them identically.
Verdict completeness. GPT-5.4 and both Sonnet versions provided an explicit overall assessment on the diff review: "Do not merge as written." GPT-5.5 reviewed each change individually but didn't give one. A reviewer relying on the model's bottom-line recommendation gets three change-level verdicts but no summary call. The output looks complete. It's missing the part that tells you what to do.
| Prompt | Dimension | Older version | Newer version |
|---|---|---|---|
| devops_incident_triage | SQL + incident template | Full, with rationale (both providers) | Shorter, more technical (both providers) |
| devops_diff_review | Overall assessment | Explicit (GPT-5.4, both Sonnet versions) | Missing (GPT-5.5) |
| devops_code_review | Production rewrite included | Yes (Sonnet 4.6) | No (Sonnet 5) |
| devops_crashloop_diagnosis | Commits to most likely cause | Yes (Sonnet 4.6) | Lists possibilities without committing (Sonnet 5) |
| Judgment Prompt | GPT 5.4 → 5.5 | Sonnet 4.6 → 5 |
|---|---|---|
| tf_security_review | Changed | Changed |
| devops_diff_review | Changed | Same |
| devops_incident_triage | Changed | Changed |
| devops_architecture_tradeoff | Same | Same |
| devops_crashloop_diagnosis | Same | Changed |
| devops_code_review | Changed | Same |
Finding 2: Before we could measure the drift, the tooling broke
Sonnet 5 broke the capture script. GPT-5.5 drove a token increase dramatic enough to expose any production ceiling you hadn't accounted for. The failures looked different but the root cause was the same: a version update changed behavior in ways the tooling wasn't built to handle.
GPT-5.5 drove a 60% increase in output tokens across the suite. Individual prompts were more extreme: tf_iam_lambda went from 350 to 2,515 tokens, incident triage from 628 to 2,086. Any token ceiling you had set would have hit. The output would have been empty or incomplete.
Sonnet 5 broke the script itself. Four failures, each with a different visibility profile.
Response shape changed (loud). Sonnet 5 returns a ThinkingBlock as the first element in message.content. The script assumed content[0] was always a TextBlock. Hard crash. Zero output. The fix was three lines:
# Before: assumes text is at index 0
"output": message.content[0].text,
# After: finds the first TextBlock regardless of position
text_block = next(b for b in message.content if hasattr(b, "text"))
"output": text_block.text,
Output truncated (silent). Sonnet 5 is more verbose than Sonnet 4.6. The code review prompt hit the 2048 token ceiling and was truncated mid-sentence. The script ran successfully. No error. The output just stopped at "Any exception." Raising max_tokens to 4096 resolved it.
Prompt missing from manifest (invisible). The incident triage prompt never ran. The script crashed after the truncated code review and the remaining prompts were skipped. The manifest recorded 8 of 9 prompts as complete. Without checking the manifest against the expected prompt list, you'd get partial results and never know they were partial.
Automated signals on incomplete output (misleading). The truncated code review still triggered numeric_score_present: true and mentions_critical: true because the fragment happened to contain "2/10" and "Critical" before it cut off. If you were running this in CI with pass/fail on automated signals, the truncated run would have looked normal. 59 words instead of 859. 12 lines instead of 206. Signals said "looks fine."
A hard crash is recoverable. You see it, you fix it, you rerun. The truncation and the missing prompt are harder: the script reports success and the manifest looks complete. The misleading signals are the worst case: the automated quality gate passes on data that isn't there. Each one is invisible until you go back to investigate.
What the data can't tell you
Providers offer dated model strings. You can pin gpt-5.4-2026-03-05 and hold the previous behavior while you investigate. That's real, and it's the right first response when drift is detected. But pinning is temporary. Providers deprecate old versions on their own timeline, not yours. And pinning only works if you're using dated strings in the first place. If your integration calls gpt-5.5 without a date, you get whatever's current with no notice. The version management discipline that software engineers take for granted with libraries doesn't exist by default here. You have to build it.
The data can't tell you which version is right either. GPT-5.4 caps at High severity. GPT-5.5 uses Critical. Sonnet was already using Critical. Which calibration is right? There's no ground truth for severity labeling on a code review. The data, without additional context or runs to compare to, can't make that call for you. That's a judgment call that requires knowing your downstream consumers.
The judgment tier of the scoring always requires a human. Automated signals caught the structural differences. They caught the truncation failures after the fact. They did not catch and cannot catch the actionability regression, the missing production rewrite, the shift from a committed diagnosis to a list of possibilities. Those require reading the output side by side and asking whether you'd act differently. That's slow. It's also the part that matters.
The system worth building
Two things changed with every version update we measured. The judgment tier drifted in ways that correctness scores couldn't detect. And the tooling built to measure the drift broke before it could run. Both happened silently. Both required instrumentation to surface.
Right now, detecting drift requires a human to run a capture, score the outputs, and compare. That works at the scale of one version update at a time. It doesn't scale to a system that's routing thousands of decisions across multiple models and needs to know in real time when the ground has shifted underneath it. The next piece explores whether you can close that loop automatically: a routing engine that knows when its own history has gone stale and re-calibrates without waiting for something to break.
Methodology How the comparison was designed and scored
Models: GPT-5.4 (gpt-5.4-2026-03-05), GPT-5.5 (gpt-5.5-2026-04-23), Claude Sonnet 4.6 (claude-sonnet-4-6), Claude Sonnet 5 (claude-sonnet-5).
Prompt suite: 9 prompts split into two tiers. Structural tier: 3 Terraform generation tasks (VPC, IAM role, S3 module). Judgment tier: 6 DevOps decision tasks (security review, diff review, incident triage, architecture tradeoff, CrashLoopBackOff diagnosis, code review).
Scoring: Each prompt scored on Correctness (1-5) and Production Readiness (1-5). Judgment prompts additionally scored on verdict, severity ranking, and "Decision Changed" (true only if a real engineer would have acted differently). I scored all prompts to maintain consistency.
Max tokens: Anthropic models used 2048 initially, raised to 4096 after Sonnet 5 truncation. OpenAI models used 4096 throughout. GPT-5.5 is more verbose than GPT-5.4.
Capture infrastructure: Python script (capture.py) run manually against each model version. Each run saves to a timestamped directory with a manifest (JSON) containing model metadata, token counts, latency, and automated signals, plus raw output files per prompt. A separate canary runs the suite on a weekly schedule via GitHub Actions to detect drift between version updates.
What this does not measure: Prompt sensitivity. The same nine prompts were used across all versions. A version that performs differently on these prompts may perform identically on others. The findings are scoped to these prompts and these task types. Generalizing from nine prompts to "this version is better or worse" would be overclaiming.
Token economics Output volume and latency across versions
OpenAI output tokens (full suite):
| Version | Total output tokens | Suite wall time |
|---|---|---|
| GPT-5.4 | ~10,150 | ~2.7 minutes |
| GPT-5.5 | ~16,500 | ~7.9 minutes |
Same prompts, 60% more tokens. Individual prompts are more extreme: tf_iam_lambda went from 350 to 2,515 tokens (7x). devops_incident_triage went from 628 to 2,086 (3.3x). Latency moved with it.
Anthropic output tokens (full suite):
| Version | Total output tokens |
|---|---|
| Sonnet 4.6 | ~10,740 |
| Sonnet 5 | ~12,100 (rerun at 4096 max_tokens) |
Sonnet 5 is modestly more verbose in token count but produces shorter prose by word count across most judgment prompts. The word count reduction averages 24% across the judgment tier. More tokens, fewer words, less actionable detail.
The capture script Design decisions and what changed during the process
Architecture: Single Python script. Supports --provider flag (anthropic or openai) and --model flag for version targeting. Outputs to timestamped run directories with manifest and per-prompt text files.
Changes required during the process:
ThinkingBlockfix: Sonnet 5 returns thinking content before text. The script was updated to find the firstTextBlockby type rather than assuming index 0.max_tokensraised from 2048 to 4096: Sonnet 5's verbosity exceeded the original ceiling, truncating the code review output.
Both changes were required before the script could produce valid comparison data. Both are examples of the problem the article describes: a model version update that breaks existing tooling without warning.
Code availability: The capture script is not published. The design decisions and prompt categories are documented in full in this article.