◉CLAUDE LABJP
●2.1.296 — Sonnet 5.5 cache reads drop to $0.10/Mtok (was $0.20), and the MCP description limit rises to 4,096 characters●2.1.295 — Hooks gain onFailure: "block", so a failed or timed-out hook no longer lets the action through●SONNET 4.5 — Retires on the Claude API on 11/30, 51 days left. Migrate to Sonnet 5.5●Q&A — A long-running report: the console jumps back to the top of history whenever output is appended●HAIKU 5.5 — Code written for Haiku 4.5 hits a 400 error on budget_tokens●NEW — When first requests keep missing, hand over a one-page description of your work first●2.1.296 — Sonnet 5.5 cache reads drop to $0.10/Mtok (was $0.20), and the MCP description limit rises to 4,096 characters●2.1.295 — Hooks gain onFailure: "block", so a failed or timed-out hook no longer lets the action through●SONNET 4.5 — Retires on the Claude API on 11/30, 51 days left. Migrate to Sonnet 5.5●Q&A — A long-running report: the console jumps back to the top of history whenever output is appended●HAIKU 5.5 — Code written for Haiku 4.5 hits a 400 error on budget_tokens●NEW — When first requests keep missing, hand over a one-page description of your work first
Articles/Claude Code
⟐ Claude Code/2026-09-11Intermediate

inferenceGatewayHeaders still works today. At noon on October 7 it falls back to a default

I read the deprecation notice that started appearing on September 10 as a spelling cleanup. After the cut-off, the old names are not ignored — they fall back to a fail-closed value or a default. Here is the old-to-new map and a script that audits your config in one pass.

Claude Code259managed-settingsconfiguration10migration9

On the morning of September 10 I opened Claude Code and found a notice I hadn't seen before: the organization's configuration used fields that are still accepted now but won't be for much longer. The Details dialog listed each field, its replacement, and the date it stops being accepted.

My first reading was that this was a spelling cleanup. If the names were simply changing, I figured I could do them all in one sitting sometime in October.

I had it backwards.

What happens on October 7 is not being ignored — it's falling back

The cut-off is October 7, 2026, 12:00 PM Pacific Time. The warning machinery shipped in the August 27 build, and per-user display began on September 10.

The part that's easy to misread is what happens afterward. If the old names were simply skipped over and everything kept working, then being late to fix them would cost almost nothing. That isn't the behavior. After the cut-off, a renamed key's old name falls back to its fail-closed value or its default.

Say you've been shipping trustBootstrapLocalExec: true to your fleet. On October 7 that true stops arriving. The drift is toward stricter, not looser. And if you have an invalid entry under managedMcpServers or orgPluginSettings, that connector or tool policy becomes unavailable until someone rewrites it.

So what changes on October 7 isn't how readable your config file is. It's what your defaults are from that day forward.

A deprecation with a deadline is not a naming problem. It's a defaults problem. Getting that backwards is exactly why I filed it one notch lower than it deserved.

One detail about the warning itself: disableConfigDeprecationWarnings suppresses the first showing, but the final reminder in the 24 hours before the cut-off still appears. Suppression only covers the opening notice — it doesn't make the deadline go away.

The old-to-new map, written down once

The mapping is in the Details dialog, but having a copy at hand makes the migration go faster. Watch for the rows where the value or the shape changes rather than the name.

Old spellingReplacementNote
inferenceGatewayHeadersinferenceCustomHeadersThe value must be a JSON object too
trustBootstrapLocalExectrustBootstrapDelivery—
enduserAttributionendUserAttributionOnly the capital E changes
isDxtEnabledisDesktopExtensionEnabled—
isDxtSignatureRequiredisDesktopExtensionSignatureRequired—
inferenceGatewayAuthScheme: "sso"inferenceCredentialKind: "interactive"The value side changes
inferenceGatewayAuthScheme: "auto"Remove the keybearer is the default
Header maps as strings or listsA JSON objectApplies to otlpHeaders, otlpResourceAttributes, bootstrapHeaders
orgPluginSettings record formThe array formBuilds before 1.15200.0 read only the record form
Tool permission "ask-session""ask"Applies to builtinToolPolicy and friends
managedMcpServers[].scopesscopeOne space-separated string
oauth as a number or stringtrue or an oauth object—

The orgPluginSettings row is the one that doesn't migrate cleanly in a single direction. Moving to the new form makes the config unreadable to older clients, so you need to confirm which builds are actually deployed before you touch it. It's the only row where the deadline and the version floor collide.

A window where neither path is read

Separate from the October 7 batch, the September 4 build moved one key: in served configuration, relaunchEnforcementHours moved out from under bootstrap and into lifecycle.

The awkward part is the read direction. Builds from September 4 onward no longer read the old path, and earlier builds do not read the new one. If your fleet isn't on a single version and you move the value, you open a window where neither side reads it.

During that window the value doesn't vanish — it reverts to the default. And in the same build, the default grace period before a required restart went from 1 hour to 24. That's a combination where you can go a while without noticing the value isn't landing.

There's one more layer: if a device-management profile sets any app-behavior key, that profile takes precedence over the served value for this key. Setting it only on the served side won't take effect. I nearly skipped past that line and spent a while moving the setting back and forth on my own machine before I reread it.

Audit the config with a check that can actually fail

There are too many items to catch by eye, and some of them sit several levels down in the nesting. I wrote the following script and started running config files through it. It picks up renamed keys, renamed values, shape violations, and the relocated path in one pass.

#!/usr/bin/env python3
"""Find spellings in managed settings that stop being accepted on 2026-10-07."""
import json, sys, pathlib
 
RENAMES = {  # old -> (new, note)
    "inferenceGatewayHeaders": ("inferenceCustomHeaders", "value must be a JSON object"),
    "trustBootstrapLocalExec": ("trustBootstrapDelivery", ""),
    "enduserAttribution":      ("endUserAttribution", "capital E"),
    "isDxtEnabled":            ("isDesktopExtensionEnabled", ""),
    "isDxtSignatureRequired":  ("isDesktopExtensionSignatureRequired", ""),
}
VALUE_RENAMES = {  # the value itself is the deprecated part
    "inferenceGatewayAuthScheme": {
        "sso":  'replace with inferenceCredentialKind: "interactive"',
        "auto": "drop the key (bearer is the default)",
    },
}
OBJECT_ONLY = ("inferenceCustomHeaders", "inferenceGatewayHeaders",
               "otlpHeaders", "otlpResourceAttributes", "bootstrapHeaders")
TOOL_POLICY_KEYS = ("builtinToolPolicy", "managedMcpServers", "orgPluginSettings")
 
 
def walk(node, path=""):
    """Yield (path, key, value) for every node, however deeply nested."""
    if isinstance(node, dict):
        for k, v in node.items():
            here = f"{path}.{k}" if path else k
            yield here, k, v
            yield from walk(v, here)
    elif isinstance(node, list):
        for i, v in enumerate(node):
            yield from walk(v, f"{path}[{i}]")
 
 
def audit(cfg):
    hits = []
    for path, key, value in walk(cfg):
        if key in RENAMES:
            new, note = RENAMES[key]
            hits.append((path, f"{key} -> {new}", note))
        if key in VALUE_RENAMES and isinstance(value, str):
            note = VALUE_RENAMES[key].get(value)
            if note:
                hits.append((path, f'{key}: "{value}"', note))
        if key in OBJECT_ONLY and not isinstance(value, dict) and key not in RENAMES:
            hits.append((path, f"{key} is a {type(value).__name__}",
                         "rewrite it as a JSON object"))
        if key == "orgPluginSettings" and isinstance(value, dict):
            hits.append((path, "orgPluginSettings is in record form",
                         "rewrite it as an array"))
        if key == "scopes" and "managedMcpServers" in path:
            hits.append((path, "managedMcpServers[].scopes",
                         "use scope: one space-separated string"))
        if key == "oauth" and not isinstance(value, (bool, dict)):
            hits.append((path, f"oauth is a {type(value).__name__}",
                         "use true or an oauth object"))
        if value == "ask-session" and any(t in path for t in TOOL_POLICY_KEYS):
            hits.append((path, '"ask-session"', 'use "ask"'))
    # Not part of the 10/7 batch: the path that moved on 2026-09-04
    if isinstance(cfg.get("bootstrap"), dict) and "relaunchEnforcementHours" in cfg["bootstrap"]:
        hits.append(("bootstrap.relaunchEnforcementHours",
                     "moved to lifecycle.relaunchEnforcementHours",
                     "builds from 2026-09-04 do not read the old path"))
    return hits
 
 
def main(paths):
    total = 0
    for p in paths:
        f = pathlib.Path(p)
        if not f.exists():
            print(f"[skip] {p} (not found)")
            continue
        try:
            cfg = json.loads(f.read_text())
        except json.JSONDecodeError as e:
            # Since September 4, an unparseable config blocks startup entirely
            print(f"[FATAL] {p}: not valid JSON -> {e}")
            total += 1
            continue
        hits = audit(cfg)
        print(f"\n=== {p} — {len(hits)} to fix ===")
        for path, what, note in hits:
            print(f"  {path}\n    {what}" + (f"\n    {note}" if note else ""))
        total += len(hits)
    print(f"\n{total} total")
    return 1 if total else 0
 
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1:] or ["managed-settings.json"]))

Run it against a file deliberately stuffed with old spellings and you get:

=== sample-managed-settings.json — 11 to fix ===
  inferenceGatewayHeaders
    inferenceGatewayHeaders -> inferenceCustomHeaders
    value must be a JSON object
  inferenceGatewayAuthScheme
    inferenceGatewayAuthScheme: "sso"
    replace with inferenceCredentialKind: "interactive"
  ...
  managedMcpServers[0].scopes
    managedMcpServers[].scopes
    use scope: one space-separated string
  bootstrap.relaunchEnforcementHours
    moved to lifecycle.relaunchEnforcementHours
    builds from 2026-09-04 do not read the old path
 
11 total

Run the rewritten config through the same script and you get zero hits and an exit code of 0. That exit code is the whole point. A check that can't fail the build is decoration, not a check. I've written the eyeball-the-output kind before, and it took me a long time to notice it had never caught anything.

Separately from deprecation, a key name you simply misspell is still swallowed without a word. I wrote that one up in A One-Letter Typo in settings.json Is Ignored Without a Single Warning. Reading the two together makes it clearer which parts of your config are validated at all — and which parts you're simply trusting.

A broken config now blocks startup

One last change, and it sits underneath the migration work itself. Since the September 4 build, if a device's managed-settings.json, one of its drop-ins, the device-management plist, or the Windows policy registry value cannot be parsed, Claude Code refuses to start and names the source.

It used to ignore the unreadable source and start anyway. That was friendlier, and it also meant you could go indefinitely without knowing a setting wasn't taking effect. Now it stops.

Which means a single trailing comma left behind mid-migration will keep sessions on that machine from starting at all. That's why the script above bails out with [FATAL] the moment parsing fails rather than continuing:

[FATAL] broken.json: not valid JSON -> Expecting property name enclosed in double quotes: line 1 column 26 (char 25)
 
1 total

Before pushing a config change out to machines, I run it through this step every time. The difference in effort between a failure you catch on one machine and a failure you catch after distribution is not small.

Where to start

Run your own managed-settings.json through the script once and look at the count. If it's zero, you're done thinking about October 7. If anything comes back, start by working out which default that particular line falls back to after the cut-off — that tells you how urgent it actually is.

If you want to make the audit a standing habit rather than a one-off, Until I planted a failing sample, my unattended checks had never once failed goes through building checks that are verified to fail, sample and all.

Deadlines have a way of turning into "I'll batch it later." Mine did. Thanks for reading.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Claude Code2026-08-25
A One-Letter Typo in settings.json Is Ignored Without a Single Warning
I diffed claude doctor output between a settings.json with misspelled keys and a correct one. There was no difference at all. Here is what actually gets validated, what slips through, and a small check that catches typos before they cost you a day.
⟐ Claude Code2026-08-21
Adding ANTHROPIC_DEFAULT_MODEL Changes Nothing While ANTHROPIC_MODEL Is Still Set
ANTHROPIC_DEFAULT_MODEL, added in v2.1.236, is not a replacement for ANTHROPIC_MODEL. Here is how the two differ, plus a Python script that lists every place a model can be pinned.
⟐ Claude Code2026-10-05
I Wrote "I'll Do the Commits" in CLAUDE.md. The Next Morning git log Had One More Line.
When Claude Code commits despite being told not to: undo with git reset --soft, then stop it physically with permissions.deny plus a PreToolUse hook. Covers the git -c form that deny rules let through, and how to test the hook without starting Claude Code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links