Uh oh!
There was an error while loading. Please reload this page.
fix(server): only run provider updates through the installer that owns the binary - #9325
fix(server): only run provider updates through the installer that owns the binary#9325juliusmarminge wants to merge 6 commits into
Conversation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…s the binary T3 Code could pick an update command from one installation while the active executable belonged to another: a standalone Codex install got `npm install -g` (#5629), a `claude-code@latest` keg got `brew upgrade claude-code` (#6245), and Homebrew casks were compared against npm's latest so `brew upgrade` could never clear the advisory (#7730). Ownership is now derived from the resolved executable's real path. Native installs run their own `update` subcommand on the resolved binary; Homebrew kegs and casks get the exact formula or cask name from the path, and their latest version from `brew info`; npm installs are pinned to the global prefix that owns the package. Anything unproven stays manual-only but still shows the version gap. Resolution is cached per instance and re-read before and after executing an update so the command matches the install at click time. Settings tracks in-flight updates per instance instead of per driver so two instances of one driver can be updated independently. Closes#5629Closes#6245Closes#7730 Co-Authored-By: Claude Code <noreply@anthropic.com>
bab148d to
7009338CompareThread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This XL PR substantially rewires provider update ownership detection, advisory resolution, command execution, post-update verification, and per-instance settings behavior across server and web code. The changes affect which installers can run on a customer machine and add cross-platform filesystem and Homebrew process probing, creating a runtime scope that requires human review. You can add or adjust custom eligibility rules. Learn more. |
Review follow-ups on the ownership resolver: - npm: only `<prefix>/lib/node_modules/<pkg>/` counts as a global install on POSIX; a project-local `node_modules` no longer yields `npm install -g --prefix <project>`. Windows keeps the shim-plus-manifest proof. - Homebrew: the keg prefix must match `brew --prefix` of the resolved `brew`, so a `Cellar` directory elsewhere is not offered `brew upgrade`. stderr is drained during probes. - A configured binary path that does not exist resolves to no context instead of a native update command it cannot run. - Codex standalone detection matches any CODEX_HOME, not just `~/.codex`. - Cursor resolves through the same cache so `fresh` re-reads the executable. - The registry reads instances from ProviderInstanceRegistry rather than the trailing live-subscription map. - Settings only offers inline updates for `behind_latest` advisories. - `catchTag` → `catchTags` per repo convention. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Co-Authored-By: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix is ON, but a cloud agent failed to start.
Reviewed by Cursor Bugbot for commit b92c85b. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Cursor's resolver returned a `cursor-agent` update for a configured binary that could not be resolved, under the same lock key, so the runner would run whatever `cursor-agent` PATH found. No resolved executable now means manual-only. The displayed command stays `cursor-agent update` like the other providers. Windows npm ownership also accepts the extensionless sh script npm writes beside `<cmd>.cmd`; the manifest-beside-shim proof is gated to win32 because a POSIX project checkout has the same shape. Co-Authored-By: Claude Code <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Quick Windows validation update: Scoop and WinGet both completed real provider updates successfully across Claude and OpenCode through the actual resolver + maintenance runner, including a same-provider Scoop/WinGet coexistence case where only the explicitly selected installation was updated. I also found one generic post-update edge case on Windows: if the updater exits successfully but the provider disappears or its installation can no longer be ownership-verified afterward, the runner can still report Separately, I found a few test-only Windows portability issues in the existing fixtures, e.g. POSIX/macOS symlink fixtures hitting |

Problem
T3 Code could pick an update command from one installation while the active executable belonged to another:
~/.codex/packages/standalone/) gotnpm install -g @openai/codex@latest, which never touches it, so the advisory looped forever ([Bug]: Codex "Update now" runsnpm install -gagainst installs it never verified are npm-managed, causing a permanent "still needs an update" loop #5629)brew install claude-code@latestkeg gotbrew upgrade claude-code, which fails ([Bug]: Provider auto-update ignores which Homebrew formula is actually installed for claude-code #6245)brew upgradecould never clear the advisory ([Bug]: Homebrew-installed Codex/Claude flagged outdated against npm latest, brew upgrade cannot clear it #7730)Fix
Ownership is derived from the resolved executable's real path, and a package manager is only invoked against an install it has evidence of owning:
<resolved binary> update…/Cellar/<name>/or…/Caskroom/<name>/brew upgrade [--cask] <name>brew info --json=v2<prefix>/lib/node_modules/<pkg>/npm install -g --prefix <prefix> <pkg>@latestThe npm prefix is pinned because the
npmonPATHcan belong to a different Node than the one that owns the provider. Native installs share npm's version train, so the registry stays authoritative for them and native users keep their launch toast.Resolution is cached per instance for an hour. The maintenance runner re-reads it fresh immediately before executing, refuses with "Provider installation changed" if the lock key moved since the advisory, and reads fresh again afterwards so a Homebrew upgrade's new latest is reflected. Settings tracks in-flight updates per instance instead of per driver.
Docs:
docs/internals/providers.mdgains a "Provider updates" section;docs/user/install.mdgains "Keeping Provider CLIs Current".Relationship to #6436
Supersedes #6436 by @ettoc00, which identified the same root cause and whose npm
--prefixpinning, Homebrew formula/cask detection, and per-instance settings state are carried over here. The rest of that PR (Scoop, WinGet, Grok native, identity hashing, wrapper-script parsing, the "Check for updates" affordance) is left out: the native-install branch there droppedlatestVersiontonull, which removed update notifications for the default Claude/Codex/OpenCode install paths, and the Vite+ detector shelled out tovp root -g, which does not exist. Scoop/WinGet support is worth its own issue with a Windows validator.Closes#5629
Closes#6245
Closes#7730
Verification
apps/server,apps/webtypecheck;vp linton touched filesbrew infocall, keg/cask/plain-/usr/local/binownership (fix(server): avoid treating Ubuntu binaries as Homebrew-managed #8832 case), cache-until-fresh, runner re-resolve ordering, lock-key-changed abort with no spawnWritten by Claude Fable 5 in Claude Code.
🤖 Generated with Claude Code
Note
Medium Risk
Changes how update commands are chosen and executed (including spawning
brewduring resolution); wrong ownership logic could block valid updates or run the wrong installer, but unproven paths fail closed to manual-only.Overview
Provider one-click updates now infer who installed the CLI from the resolved executable and its real path, instead of assuming npm/Homebrew defaults. Unproven installs stay manual-only (
canUpdate: false) while still showing version gaps; proven paths get the matching command (nativeupdate,brew upgrade [--cask] <name>withbrew infofor “latest”, npm with pinned--prefix, pnpm/bun/vp globals). Codex gains standalone-installer updates; Cursor binds updates to the resolvedcursor-agentpath.API shape: snapshots expose
resolveMaintenance()(hourly cache,{ fresh: true }to re-derive) instead of a staticmaintenanceCapabilities. The registry resolves maintenance from the instance registry (not stale reconciliation state) and supports optionalfresh. The update runner re-resolves before spawn and aborts if the lock key changed (“Provider installation changed”), then re-resolves after success for post-upgrade advisories.UI: provider settings track in-flight updates per instance and show inline “Update now” when
canUpdateis true for that instance.Docs add provider-update internals and user-facing “Keeping Provider CLIs Current.”
Reviewed by Cursor Bugbot for commit 98c0e32. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Run provider updates only through the installer that owns the binary path
maintenanceCapabilitiesvalue onServerProviderShapewith a cachedresolveMaintenanceeffect that performs path-based resolution.providerMaintenanceRunner.updateProvidernow performs a fresh capability resolution before command execution and aborts the update if the lock key differs from the cached capability.Macroscope summarized 98c0e32.