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 explicitlywalk() 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 spelling | Replacement | Box |
|---|---|---|
inferenceGatewayHeaders | inferenceCustomHeaders | A |
trustBootstrapLocalExec | trustBootstrapDelivery | A |
enduserAttribution | endUserAttribution | A |
isDxtEnabled | isDesktopExtensionEnabled | A |
isDxtSignatureRequired | isDesktopExtensionSignatureRequired | A |
inferenceGatewayAuthScheme: "sso" | inferenceCredentialKind: "interactive" | A |
inferenceGatewayAuthScheme: "auto" | remove the key (bearer is the default) | A |
ask-session (tool permission value) | ask | A |
managedMcpServers[].scopes | scope (one space-separated string) | A |
transport: "builtin" | remove it (built-in entries take no transport) | A |
authorityHost | azureCloud: "us-gov-high" for a GCC High tenant | A |
orgPluginSettings record form | array form | B |
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.