◉CLAUDE LABJP
●THREADS — Message threads arrived in beta on the Messages API (11/2): the server keeps the conversation and you send only new messages●API — The Messages API maximum request size grew from 32 MB to 128 MB (10/28)●SONNET 4.5 — Retires on the Claude API on 11/30, 26 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST — Opus 4.8 fast mode is deprecated and will be removed on 11/30. Migrate to Opus 5.5●NEW — I Put a Ten-Line Promise File at the To…●THREADS — Message threads arrived in beta on the Messages API (11/2): the server keeps the conversation and you send only new messages●API — The Messages API maximum request size grew from 32 MB to 128 MB (10/28)●SONNET 4.5 — Retires on the Claude API on 11/30, 26 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST — Opus 4.8 fast mode is deprecated and will be removed on 11/30. Migrate to Opus 5.5●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-09-21Intermediate

The morning a deprecation notice appeared, I checked versions before editing config

Claude Desktop and Cowork stop accepting older managed-configuration spellings on October 7. Here is how I sorted mine into what to fix now, what to fix after upgrading, and what changes meaning on its own.

managed configurationdeprecation4migration14Vertex AI3MCP60

A quiet notice appeared in the corner of the screen just after I finished setting up a client's Mac for a website project I had taken on: the organization's configuration uses a deprecated field. Opening Details gave me a list — field name, replacement, cut-off date. October 7, 2026, 12:00 PM Pacific Time.

My first instinct was to fix all of it right then. Most entries looked like plain renames, the kind a find-and-replace handles in under a minute.

What stopped me was re-reading the Claude Desktop and Cowork changelog. One of the entries carried more than a replacement name. It carried a condition: update the fleet first.

Upgrade first, then rewrite. If a deadline pushes you into editing the configuration file alone, the machines still running the older build break before the deadline arrives, not after.

One deadline, three different moments to act

Looking at the list again, I sorted the entries into three boxes.

The first box holds things safe to change today. inferenceGatewayHeaders becomes inferenceCustomHeaders; isDxtEnabled becomes isDesktopExtensionEnabled. Older builds already understand the new spellings, so there is no cost to moving early.

The second box holds things that need the client upgraded first. Moving orgPluginSettings from its record form to its array form belongs here. Desktop versions before 1.15200.0 read only the record form and do not enforce plugin tool locks when handed an array. Change the config alone and you create machines where the restriction you intended silently stops applying — right now, weeks ahead of the cut-off.

The Vertex AI inferenceCredentialKind sits in the same box. If you deliver configuration as nested JSON — a self-hosted bootstrap server, or a Setup JSON export — an older desktop drops the Google client ID out of a nested interactive credential and Vertex sign-in stops working. Keeping oauth until the whole fleet is current turned out to be the correct move — though flat MDM keys, .mobileconfig and .reg files are unaffected. How you deliver the config determines how exposed you are.

The third box is the one I nearly missed. Some settings change meaning on October 7 without anyone editing them. A Vertex configuration that sets interactive together with inferenceVertexWorkforceAudience and no inferenceVertexOAuthClientId is read today as Workforce Identity. After the cut-off, the same bytes mean Google sign-in. Nothing in the file changes; only the interpretation does. If Workforce is what you meant, workforce has to be stated explicitly.

Counting what is left, box by box

Three boxes were more than I could hold in my head, so I turned the sorting into a script. Point it at your managed-configuration JSON and it reports counts and locations per box.

#!/usr/bin/env python3
"""Sort deprecated managed-config spellings by when it is safe to fix them.
Usage: python3 audit_config.py bootstrap.json [more.json ...]
Exit codes: 0 = nothing found / 1 = box B or C present / 2 = box A only"""
import json, sys
 
# (old spelling, replacement, box)
#   A = safe now / B = upgrade the client first / C = changes meaning on its own
RENAMES = [
    ("inferenceGatewayHeaders", "inferenceCustomHeaders",              "A"),
    ("trustBootstrapLocalExec", "trustBootstrapDelivery",              "A"),
    ("enduserAttribution",      "endUserAttribution",                  "A"),
    ("isDxtEnabled",            "isDesktopExtensionEnabled",           "A"),
    ("isDxtSignatureRequired",  "isDesktopExtensionSignatureRequired", "A"),
    ("authorityHost",           'azureCloud: "us-gov-high"',           "A"),
]
HEADER_MAPS = ["inferenceCustomHeaders", "otlpHeaders",
               "otlpResourceAttributes", "bootstrapHeaders"]
 
 
def walk(node, path=""):
    """Flatten nested dicts and lists into (path, key, value) triples."""
    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):
            here = f"{path}[{i}]"
            yield here, None, v
            yield from walk(v, here)
 
 
def audit(cfg):
    found = []
    for path, key, value in walk(cfg):
        if key is None:
            continue
        for old, new, bucket in RENAMES:
            if key == old:
                found.append((bucket, path, f"{old} -> {new}"))
        # header maps have to be JSON objects, not strings or lists
        if key in HEADER_MAPS and not isinstance(value, dict):
            found.append(("A", path,
                          f"{key} is a {type(value).__name__} -> use a JSON object"))
        # record-form orgPluginSettings moves to the array form, fleet first
        if key == "orgPluginSettings" and isinstance(value, dict):
            found.append(("B", path,
                          "orgPluginSettings is in record form -> array form "
                          "(update the fleet past 1.15200.0 first)"))
        # ask-session becomes ask
        if key in ("builtinToolPolicy", "toolPolicy", "permission"):
            vals = value.values() if isinstance(value, dict) else [value]
            if any(v == "ask-session" for v in vals):
                found.append(("A", path, "ask-session -> ask"))
        # Vertex: with nested delivery, keep oauth until the fleet is current
        if key == "inferenceCredentialKind" and value == "oauth":
            found.append(("B", path,
                          'inferenceCredentialKind: "oauth" -> "interactive" '
                          "(nested delivery: upgrade everything first)"))
    # changes meaning on 2026-10-07 even if untouched
    if (cfg.get("inferenceCredentialKind") == "interactive"
            and "inferenceVertexWorkforceAudience" in cfg
            and "inferenceVertexOAuthClientId" not in cfg):
        found.append(("C", "(top level)",
                      "read as Workforce Identity today, as Google sign-in "
                      "after 2026-10-07 -> state workforce explicitly"))
    return found
 
 
def main(paths):
    buckets = {"A": [], "B": [], "C": []}
    for p in paths:
        with open(p, encoding="utf-8") as fh:
            cfg = json.load(fh)
        for bucket, path, msg in audit(cfg):
            buckets[bucket].append(f"{p}:{path}  {msg}")
    labels = {"A": "A safe to rewrite now",
              "B": "B upgrade the client, then rewrite",
              "C": "C changes meaning after the cut-off on its own"}
    for b in ("A", "B", "C"):
        print(f"\n## {labels[b]}  ({len(buckets[b])} found)")
        for line in buckets[b] or ["  (none)"]:
            print("  " + line if not line.startswith("  ") else line)
    if buckets["B"] or buckets["C"]:
        return 1
    return 2 if buckets["A"] else 0
 
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Feeding it a file with one of each produces this:

## A safe to rewrite now  (4 found)
  sample.json:inferenceGatewayHeaders  inferenceGatewayHeaders -> inferenceCustomHeaders
  sample.json:otlpHeaders  otlpHeaders is a str -> use a JSON object
  sample.json:isDxtEnabled  isDxtEnabled -> isDesktopExtensionEnabled
  sample.json:managedMcpServers[0].toolPolicy  ask-session -> ask
 
## B upgrade the client, then rewrite  (1 found)
  sample.json:orgPluginSettings  orgPluginSettings is in record form -> array form (update the fleet past 1.15200.0 first)
 
## C changes meaning after the cut-off on its own  (1 found)
  sample.json:(top level)  read as Workforce Identity today, as Google sign-in after 2026-10-07 -> state workforce explicitly

walk() recurses because the older spellings tend to sit deep inside managedMcpServers. When I was checking only top-level keys with in, I was missing every toolPolicy nested in the array. The three-way exit code exists so CI can pass a box-A-only result and page a human for B or C.

Old name, new name

Pulling out just the renames makes the difference between "replaceable" and "not replaceable" visible.

Old spellingReplacementBox
inferenceGatewayHeadersinferenceCustomHeadersA
trustBootstrapLocalExectrustBootstrapDeliveryA
enduserAttributionendUserAttributionA
isDxtEnabledisDesktopExtensionEnabledA
isDxtSignatureRequiredisDesktopExtensionSignatureRequiredA
inferenceGatewayAuthScheme: "sso"inferenceCredentialKind: "interactive"A
inferenceGatewayAuthScheme: "auto"remove the key (bearer is the default)A
ask-session (tool permission value)askA
managedMcpServers[].scopesscope (one space-separated string)A
transport: "builtin"remove it (built-in entries take no transport)A
authorityHostazureCloud: "us-gov-high" for a GCC High tenantA
orgPluginSettings record formarray formB
inferenceCredentialKind: "oauth" (Vertex)"interactive"B

Header maps written as strings or lists — in inferenceCustomHeaders, otlpHeaders, otlpResourceAttributes and bootstrapHeaders — have to become JSON objects too. Mine had otlpHeaders as a single string, which was the easiest thing on the whole list to skim past.

After the cut-off, values fail closed rather than getting ignored

This is where my estimate was wrong. I assumed an expired spelling would simply be skipped. It is not. A renamed key's old name falls back to its fail-closed value or default, and an invalid managedMcpServers or orgPluginSettings entry makes that connector or tool policy unavailable until it is rewritten.

The handling of unreadable configuration values changed in the same direction. A value the app cannot read now engages the restriction it belongs to instead of being ignored. disabledBuiltinTools, builtinToolPolicy, coworkTabEnabled and disableBundledSkills fall back to their restrictive value; an unreadable managedMcpServers keeps Code sessions restricted to managed MCP servers; an unrecognized tool-permission value is applied as the most restrictive setting — ask for a built-in tool, blocked for a plugin-delivered tool — and reported as a configuration error.

Put differently: the failure mode is not "it stops working," it is "something that should be available quietly is not there." That takes longer to trace back to a config file.

That asymmetry is also what decides how urgent box B is for you. Before moving orgPluginSettings to the array form, I wanted a number rather than an assumption, so I checked the version actually deployed rather than the version I had packaged — the About panel on a representative machine, and the inventory report from whatever manages the fleet, which is where the stragglers show up. A single machine sitting two releases behind is enough to lose a plugin tool lock, and nothing in the deprecation notice tells you that machine exists. If your configuration arrives as flat MDM keys, a .mobileconfig or a .reg file, the Vertex half of box B does not apply to you at all, and the version check only needs to cover orgPluginSettings.

You can dismiss the warning, but not the final reminder

The deprecation notice has been showing since September 10, 2026, and appears once more in the 24 hours before a field stops being accepted. disableConfigDeprecationWarnings hides the first showing; the final reminder still arrives. The Details dialog names each field, its replacement and the cut-off date, and a Copy report button puts a plain-text administrator summary on the clipboard. I used that summary as the raw material for my sorting.

The Setup window now carries the same notice under any setting scheduled to stop being accepted. Pressing Copy report once and keeping the text turned out to be far more useful than dismissing the notice and trusting myself to remember.

There is a second, later deadline worth putting on the calendar. An undocumented client-certificate fallback setting is no longer read by releases from 1.49585.0 on. Deployments that still set it keep working, but users see an in-app warning from November 3, 2026, and the setting stops being accepted on November 17. Two dates, not one — so I added both.

What you can do today is count

My own order was: count, fix box A, check the deployed version, fix box B, put the box C date on the calendar. Only the first two actually happened; the rest is scheduled for the end of September. If you are not on Vertex, boxes B and C come back empty, and the first two steps are the whole job.

Open one managed-configuration JSON and count what lands in box A. If the answer is zero, this deadline asks nothing of you.

If you do run through Vertex, reviewing the client ID and audience pairing now is what makes October 7 uneventful. I wrote about the platform-side configuration in Vertex AI × Claude Enterprise Integration Guide: Prompt Caching, Multimodal, and Agent Design if that is useful alongside this.

Deadlines pull attention toward finishing on time. Checking whether it is the right moment to touch something at all, before finishing on time — that is the step I intend to keep.

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 $10 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

⬡ API & SDK2026-11-05
Three Changes I Made Before Moving to Message Threads, So My Own Copy of the Conversation Stays Mine
What changes when a loop that resends the full history every turn moves to message threads, which entered beta on November 2. How create and continue differ, how to triage a 404 or 400, and where I would not migrate, all based on the official docs.
⬡ API & SDK2026-10-31
Days Vanished From My Daily Table After Moving to the Usage and Cost Analytics API — Why Empty Days Don't Come Back, and How to Zero-Fill
The Usage and Cost Analytics API (beta since 10/29) returns no rows for days with no usage. Here is why a drop-in swap from the legacy report leaves gaps, the order I'd check things in, and a small Python zero-fill, tested against mock data.
⬡ API & SDK2026-10-27
Three Places Python SDK 2.0.0 Breaks Your Code — A Checking Order Built on a Mocked Transport
Upgrading to Python SDK 2.0.0 changes behavior in three places: the temperature argument, the auth header, and double credentials. Here are the outputs I reproduced on a mocked transport, the fix for proxy setups, and the order I check in.