The parameter Claude 5 will not take — and the fix that was worse than the bug
Every turn on a workspace 400'd because the harness still sent temperature. The one-line guard shipped in an hour. Writing it up found two defects in it, one of which silently made every default call non-deterministic. Release notes 2026-09-15 — vocion-core#346, #355.

Status: shipped, both halves. vocion-core #346 (3a934c9c) and its follow-up #355 (bd05027c) are merged onto the release branch release/2026-09-15-rail and deployed to a deployment we operate. The pull requests are still landing on main individually behind that release branch.
The loud bug first, because it is thirty seconds of reading if you run your own harness against Anthropic models.
A workspace moved its agents to claude-sonnet-5. Every chat turn on it came back:
400 invalid_request_error: `temperature` is deprecated for this model
Not some turns. Every turn, immediately, on a workspace that had worked five minutes earlier. Claude 4.7 and later — and the whole 5 family — removed the sampling parameters. temperature, top_p and top_k are not ignored there; they are refused. So a harness that has always sent temperature works perfectly until the day somebody changes a model id in a config, and then it fails on 100% of requests with an error that reads like a deprecation warning rather than a hard stop.
The fix is obvious: don't send it to those models. That took an hour.
Then we sat down to write this post, and the post found two things wrong with the fix.
The quiet one: the guard was one generation too wide
The first version of the predicate was this:
/claude-(?:sonnet-4-6|opus-4-[678]|sonnet-5|opus-5|fable-5|mythos-5)/
Read the first alternative. It matches 4.6 — Sonnet 4.6 and Opus 4.6. And 4.6 is the generation that deprecated sampling parameters without removing them. It still honours what you send it. Only 4.7 and up refuse.
That would be a harmless over-match, except for one fact: claude-sonnet-4-6 is the default main model (DEFAULTS in libs/llm/langchain.ts). So the guard that was written to stop a loud failure silently stopped sending temperature: 0 on every default call in every deployment — and the provider default is not zero.
Which means: work that had been deterministic quietly became non-deterministic. No error. No log line. No 400 to page anyone. Nothing in a dashboard. The only way to notice is to run the same prompt twice and be surprised — and the whole class of work most likely to be affected (classifiers, extractors, anything with a fixed expected output) is the work you are least likely to re-run by hand.
That is a worse defect than the one it was shipped to fix, and it would have been invisible for as long as nobody happened to look.
The line belongs at 4.7, where the API actually starts refusing:
/claude-(?:opus-4-[78]|sonnet-5|opus-5|fable-5|mythos-5)/
The loud one, again: there were two transports, and only one was guarded
anthropicOmitsSampling had exactly two references in the tree: its definition, and the ChatAnthropic construction. The same function builds a ChatBedrockConverse a few lines further down, for the same models over a different transport — and that branch still passed temperature unconditionally.
Bedrock is a different door to the same model. It refuses the same parameters. So after the "fix", a 5-family model reached through Bedrock still 400'd on every turn, exactly as before, and the Bedrock default main model is us.anthropic.claude-sonnet-4-6. The predicate had always been written to see through a decorated Bedrock id; nothing called it there.
Both branches now spread the same guard:
// Bedrock is a different transport to the same models, so it refuses
// the same parameters.
...(anthropicOmitsSampling(model) ? {} : { temperature }),
What makes this stick
A regex with two off-by-one-generation failure modes, one loud and one silent, deserves a test that states the line rather than a comment describing it. libs/llm/samplingSupport.test.ts asserts all five cases:
it('KEEPS them for 4.6, which still honours what it is sent', () => {
// `claude-sonnet-4-6` is the default main model. Omitting the parameter
// here would move every default call off temperature 0 without anyone
// asking for it — the bug this test exists to prevent coming back.
expect(anthropicOmitsSampling('claude-sonnet-4-6')).toBe(false);
expect(anthropicOmitsSampling('claude-opus-4-6')).toBe(false);
});
— plus the 5 family, 4.7 and 4.8, older Claude, and Bedrock-decorated ids on both sides of the line (us.anthropic.claude-sonnet-5-v1:0 → true, us.anthropic.claude-sonnet-4-6-v1:0 → false).
Two things still worth checking in your own harness
The extended-thinking branch. It returns earlier in the same function and still sets temperature: 1, because enabling thinking with a fixed token budget required exactly that on the models it was written for. The same generation that removed sampling parameters also removed budget_tokens — the replacement is thinking: { type: 'adaptive' } plus an effort level. The file already carries a comment saying the flag must be revisited if the main model is pointed at 4.7 or later. It has not been.
The shape of the class of bug. "Stop sending X to models that reject it" is a guard on a set, and a guard on a set has two ways to be wrong. Getting the boundary wrong in the permissive direction fails loudly the next time somebody upgrades. Getting it wrong in the restrictive direction changes behaviour on models that were working, and says nothing. We shipped the second kind and found it by writing a paragraph explaining the regex.
What changed for operators
Nothing to configure — there is no flag and no env var, the behaviour follows the model id. If you were running the release branch between 3a934c9c and bd05027c, calls on a 4.6 model went out without temperature: 0 for that window; from bd05027c they are pinned again.
Where this lives in the repo
vocion-core#346 (3a934c9c) and #355 (bd05027c), on release/2026-09-15-rail.
packages/core/src/libs/llm/langchain.ts—anthropicOmitsSampling, both branches ofbuildChatModel, and theDEFAULTStablepackages/core/src/libs/llm/samplingSupport.test.ts— the five cases above
Not verified: the Bedrock path has not been exercised against live Bedrock. What is verified is that the predicate now gates both construction sites and that the decorated ids resolve correctly on both sides of the 4.7 line.
Related: Model upgrade test and Release notes — 2026-09-15 (afternoon).