Update to a new upstream Claude Desktop version
When to run this: normally you don't. CI auto-releases new upstream versions (version-check.yml dispatches build-and-release.yml; green run = released + .upstream-version bumped + tracking issue closed). Run this skill when the auto-release failed (a comment on the "new version detected" issue links the failed run) or when you want a deep audit of a bump. On a patch failure, decide FIRST: did the re-minify just move the anchor (fix the regex), or did upstream natively implement what we patch (remove the patch — the expected direction, since Anthropic maintains 1p Linux support; do NOT convert to a regression guard)?
Run from the repo root. The official Linux .deb is remotely managed and re-minifies every release; patches use [\w$]+ wildcards on stable string anchors. Step 1 runs /fresh-upstream for a clean extract; Step 9 ends with /deploy to release. $ARGUMENTS may name a target version.
Use sequential thinking and delegate independent analysis (diff, flag audit, ion-dist, platform gates) to parallel sub-agents where useful; you coordinate and edit.
Step 0 - clean slate
cd "$(git rev-parse --show-toplevel)"
git status # must be clean before starting
rm -rf build/ extract/ # old artifacts
Note current .upstream-version. If you're here because the auto-release failed, read the failed run's log first — it names exactly which patch (and which sub-patch counter) failed.
Step 1 - build & fix patches
Run the build for this OS (Arch here). It auto-downloads the latest official .deb, applies patches, packages:
./scripts/build-local.sh
# Ubuntu/Debian: SKIP_SMOKE_TEST=1 ./scripts/build-ubuntu-local.sh
# Fedora/RHEL: ./scripts/build-fedora-local.sh
If patches fail (upstream renamed identifiers / refactored / removed a feature):
- Get a clean unpatched extract (run
/fresh-upstream, or it's already in./tmp/app.asar.contents/.vite/build/). The main bundle is code-split since v1.19367.0:index.jsis a loader stub, the code lives inindex.chunk-<hash>.jssiblings (hashes change every release) +index.pre.js;rgacrossindex*.js, not justindex.js. - For each failing patch, find the new pattern with
rg, then fixpatches/<group>/<name>.nimusing[\w$]++ capture/replace. Edit the.jsinjs/for Computer Use / cowork-font (they'restaticReadinto the patch). - Recompile + test one patch against the stub+chunks concatenation (what the orchestrator stages):
cd patches && make <patch_name> && cd .. B=./tmp/app.asar.contents/.vite/build { cat $B/index.js; for c in $B/index.chunk-*.js; do printf '\n/*__CDB_SPLIT__%s__*/\n' "$(basename $c)"; cat "$c"; done; } > /tmp/test-index.js patches/<group>/<patch_name> /tmp/test-index.js; echo "exit=$?" node --check /tmp/test-index.js - Re-run the build until all patches pass. Every sub-patch must succeed or
quit(1)- never[WARN]+continue. If a feature was upstreamed, remove the patch (git rm) - once nothing of ours is injected, a patch that only asserts upstream's own behavior is not maintaining our modification. See AGENTS.md Rule 4/6. Pure assert-only regression-guard patches were retired 2026-07-15.
Step 2 - Linux-compat analysis (new gates?)
Diff old vs new for newly darwin/win32-gated features that need Linux support (set OLD/NEW to the two index.js paths):
diff <(rg -o '.{0,40}process\.platform.{0,40}' "$OLD"|sort -u) <(rg -o '.{0,40}process\.platform.{0,40}' "$NEW"|sort -u)
diff <(rg -o '.{0,80}status:"unavailable".{0,80}' "$OLD"|sort -u) <(rg -o '.{0,80}status:"unavailable".{0,80}' "$NEW"|sort -u)
rg 'process\.platform\s*[!=]==?\s*"(darwin|win32)"' "$NEW" | grep -v linux
diff <(rg -o 'require\("[^"]+"\)' "$OLD"|sort -u) <(rg -o 'require\("[^"]+"\)' "$NEW"|sort -u) # new native modules → need x86_64+aarch64
Validate any new input/screenshot feature across all 5 session types (X11, wlroots, GNOME, KDE, XWayland).
Step 3 - diff old vs new JS
File-level diff (new/removed files in app.asar), IPC handler diff (handle("...")), Claude Code / Cowork subsystem changes. Classify each finding: rename-only / new feature / changed behavior / removed. (Prompt 2 in update-prompt.md.)
Step 4 - feature-flag audit
Read baseline/CLAUDE_FEATURE_FLAGS.md. Find the new static-registry function name (changes every release). Diff flag sets:
diff <(rg -o '\w+\("[0-9]{6,}"\)' "$OLD"|sort -u) <(rg -o '\w+\("[0-9]{6,}"\)' "$NEW"|sort -u)
Check enable_local_agent_mode.nim's override list still covers the cowork/code flags and its Zod schema includes them. Update the doc + version-history table.
Also refresh the flag catalog in js/growthbook_overrides.js (TEMPLATE): it lists every store-consulted flag of the audited version, commented out. Extract IDs with rg -o '[\w$]+(?:\.[\w$]+)*\("([0-9]{6,10})"[,)]' -r '$1' <new-bundle-concat> | sort -u, drop the IDs force-rewritten by patches (rg -oI '"[0-9]{6,10}"' patches/*/*.nim, minus IDs only mentioned in comments), and update descriptions + the version stamp in the header. Then regenerate the browsable copy and rebuild the binary:
bash scripts/check-jsonc-template-sync.sh --write # updates docs/claude-desktop-extra.jsonc
touch patches/core/add_growthbook_overrides.nim && (cd patches && make) # Makefile doesn't track staticRead deps
CI runs scripts/check-jsonc-template-sync.sh (no --write) and fails if the docs catalog drifts from the shipped template.
Step 5 - ion-dist SPA audit
Read baseline/ION.md. Confirm ion-dist exists in new resources; compare bundle stats + content-hash of the config chunk; confirm fix_ion_dist_linux.nim patterns (mountPath linux key, platform ternary) still match (it finds the target by content signature, not filename). Update ION.md/the patch if changed. (Prompt 4.)
Step 6 - platform-gate re-audit
Read baseline/PLATFORM_GATE_BASELINE.md. Re-count darwin/win32/linux gates; reclassify any new ones PATCHED/NATIVE/STUB/PORTABLE. Any PORTABLE = a new Linux-support opportunity. Update the doc. (Prompt 5.)
Step 7 - Cowork backend
Cowork now runs on the .deb's bundled native VM backend (cowork-linux-helper + virtiofsd + smol-bin + QEMU/OVMF; requires /dev/kvm). The old claude-cowork-service Go daemon is deprecated and out of scope - no cross-dependency to check.
Step 8 - docs
Update only what changed:
CHANGELOG.md- add the new entry (one##section per day, newest at top; informative not a debug log).baseline/CLAUDE_FEATURE_FLAGS.md,CLAUDE_BUILT_IN_MCP.md,ION.md,PLATFORM_GATE_BASELINE.md- if their tracked internals moved.PATCHES.mdpatch tables - if patches added/removed/changed; the README's three-bullet summary carries the per-directory counts. Do NOT touch README install-command version numbers (CI updates those viased; manual edits cause merge conflicts).
Step 9 - bump .upstream-version (required) + commit
echo "<NEW_VERSION>" > .upstream-version # closes the "new version detected" issue, greens the README badge
Then commit + push to master directly (per global CLAUDE.md), only when the user says to. After merge, release with /deploy (or /deploy force for patch-only changes where upstream version didn't move).
Guardrails
- Always re-extract fresh if
./tmpis stale (different minified names → wrong conclusions). node --checkafter every patch edit.- A green build with all patches applied + docs synced +
.upstream-versionbumped is "done". Don't claim done with failing patches.