🤖 feat: let mux server update itself under a restart supervisor - #4083
Conversation
…alog ServerUpdater fills the UpdateService impl slot in CLI mode. It recognizes the coder/mux launcher layout (external bin symlink into a bun/npm/pnpm install) and a declared restart supervisor, checks the npm dist-tag for the effective channel, stages the exact version into a sibling xum-staging-<version> dir, and on install atomically re-points the launcher symlink and runs the SIGTERM cleanup path so the supervisor relaunches the new version. The restart is refused while volatile server-owned work exists (active streams, pending turn work, queued messages, pending auto-retry, open or starting terminals, running background processes or direct commands), and shutdown now latches new session, terminal, and command admission before the first await. Unrecognized layouts report unsupported and every action no-ops. The About dialog works in browser mode (unsupported reason, install-blocked blockers, indeterminate download), and browser clients reload when GET /version differs from the bundled build after reconnecting. --- _Generated with `xum` • Model: `anthropic:claude-fable-5-1` • Thinking: `xhigh` • Cost: `$60.84`_ <!-- mux-attribution: model=anthropic:claude-fable-5-1 thinking=xhigh costs=60.84 -->
… close the install race Stage verification no longer depends on a .bin shim (pnpm shims and the hardcoded mux name go away); the smoke run uses process.execPath. The installing latch is taken before the blocker snapshot so two concurrent install calls cannot both activate. Existing sessions stay reachable during shutdown; only new session creation is refused. Registry override resolves through the XUM/MUX compatibility resolver.
…ed process UAT found that a server running with a generated auth token relaunches with a new token, so every browser session lands on the auth page after an update. Self-update now reports unsupported unless the token is stable (MUX_SERVER_AUTH_TOKEN, --auth-token, or --no-auth). It also refuses when the process was started from the entry file directly even if MUX_BINARY names a launcher, because the supervisor would relaunch the old path.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 67efeacddc
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67efeacddc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…try trust - Refuse plaintext registries except on loopback: the staged package is executed by the smoke run, so a network registry must be TLS-protected. - Count workspaces whose background init is still running as a blocker. - Refresh lazily tracked background process statuses before the synchronous blocker snapshot so a naturally exited command cannot block restarts indefinitely. - Give /version the API CORS treatment so the reconnect version probe works when the frontend is served from another origin.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4d2e364bd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…utdown - Count workspaces from the start of provisioning (init abort controllers), not only from init settlement registration. - Require HTTPS registries outright; loopback plaintext can still traverse an inherited HTTP proxy. - Build the proxy dispatcher on first use so a malformed proxy variable is a check error instead of a startup crash. - Refuse to prune stages when the launcher no longer points at the running entry. - Abort a pending staged install during shutdown and wait for it to settle so a detached package manager cannot outlive the server.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e9ebe94df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
…tch shutdown - Resolve the published mux forwarding shim (bin/mux.js) to the @coder/xum entry so the registry module layout is recognized under every package manager; activation and pruning use the same resolution. - Block restarts while workspaces are being removed, archived, forked, or staged, and refuse new removals and archives once shutdown has begun. - Latch the updater on shutdown so no check, download, or install can start a detached installer after cleanup began. - Reload the browser when git_describe differs too, so a stable build on the same commit as a nightly is picked up. - Probe /version only when the bundle is served from the backend origin; split-origin dev bundles and extension webviews cannot be refreshed by a reload. Generate src/version.ts before the VS Code extension build. - Document that registries must be HTTPS without credentials.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 1d4d584a1f
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d4d584a1f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… gate - Verify a staged package with node --check instead of executing it; nothing from the registry runs until the operator activates it. - Always re-point the launcher the process was started through (argv[1]); a declared XUM_BINARY/MUX_BINARY may only confirm that path. - Count renames and context mutations as lifecycle blockers and refuse new renames once shutdown has begun. - Pass --no-global to npm so inherited global config cannot redirect the stage. - Roll the runtime channel back when persisting the preference fails.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 479d7b80e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… and requests Restart blockers now count in-process workflow runs, project clones and git inits, live or starting PortableDesktop sessions, and every other RPC call still in flight (an oRPC middleware counts calls; the install call is exempt). ProjectService latches new clones, creations, inits, and removals once teardown starts. Channel rollback restores the updater's inferred channel rather than the config fallback, pnpm staging installs pass --no-global, and the reconnect /version probe runs after the reconnected client is published.
Resolves import and field adjacency conflicts in serviceContainer.ts, workspaceService.ts, serviceContainer.test.ts, and DesktopSessionManager.ts. The restart-blocker test drives the new TurnCoordinator API instead of the removed setTurnPhase setter.
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 849e0d6ade
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 849e0d6ade
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Redirected metadata can name a dependency anything and point it at any published tarball or at the release tarball itself, so a manifest-supplied name let the attacker choose which registry digest an entry was compared against. Every parser now verifies the name the dependent requested and rejects aliases: npm's folder-derived name must match any recorded "name", pnpm's dependency edges must be plain versions, and the local exemption applies only to the top-level release entry by lockfile key. Bundled flags no longer exempt an entry; the release ships no aliased or bundled packages.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b01d8eae9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- The stage directory keeps its mkdtemp name for its whole life instead of being renamed to xum-staging-<version>; rename() would replace an empty foreign directory of that name, and a populated one used to block the stage. Pruning already handles suffixed names. - UpdateService.setChannel chains each change behind the previous one so persist, runtime switch, and rollback cannot interleave across callers and leave the config and the runtime on different channels.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f8cd7e020
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…nstalls Activation relies on POSIX rename() replacing the launcher symlink and the staged entry check on a POSIX executable bit, so resolveInstallLayout now refuses Windows up front instead of letting every install attempt fail. npm gets --include=optional so an inherited omit cannot drop the optional platform packages; pnpm and bun have no flag that overrides an inherited optional=false, which the installCommand comment records.
|
@codex review |
An inherited optional=false (npm_config_optional or a user .npmrc) makes pnpm omit the optional platform packages while still recording them in pnpm-lock.yaml, so verification cannot notice; --config.optional=true overrides the inherited value.
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
💡 Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: a25c2f45e2
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a25c2f45e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The palette command reported success while nothing was saved, so an operator who then met the reported requirement restarted on the old channel. The preference is now persisted and recorded; status stays unsupported and nothing else runs.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
## Summary Version bump for the v0.28.5 patch release. Headline changes since v0.28.4: remote server connections in the desktop app (coder#4101), self-updating `xum server` under a restart supervisor (coder#4083, coder#4127), first-class GPT-6 Astra and Astra Pro support including Codex OAuth routing (coder#4064, coder#4094, coder#4106, coder#4124), token-budget context window rollovers (coder#4097), the workspace remembering model and mode on send (coder#3968), in-place plugin updates (coder#4164), the optional flat sidebar chat list (coder#3994), and copying selected chat text as Markdown (coder#4170). It also carries a long run of streaming, compaction, and task-lifecycle fixes (reconnect streaming coder#4123, message edits during active streams coder#4153, Codex OAuth prompt-cache routing coder#4159, compaction/history fencing coder#4133 through coder#4148, task lock ordering coder#4161) plus the Effect Wave 4 runtime refactors and deslop passes 1 through 3. ## Implementation Bumped with `node ./scripts/set-package-version.js 0.28.5` so the root `package.json` and the legacy `packages/mux-compat` forwarding package stay version-locked. `src/common/compat/productIdentity.test.ts` passes locally (8/8). After this PR merges, the `v0.28.5` tag will be applied to the squash commit and the GitHub Release published to trigger the desktop/npm/docker pipelines. --- _Generated with `xum` • Model: `anthropic:claude-fable-5-1` • Thinking: `xhigh` • Cost: `$1.64`_ <!-- mux-attribution: model=anthropic:claude-fable-5-1 thinking=xhigh costs=1.64 -->
Summary
mux servercan now update itself from the About dialog (or the matching command palette actions) when it runs under a restart supervisor with the launcher layout thecoder/muxregistry module creates. The server checks the npm dist-tag for its channel, downloads and digest-checks the release, stages the exact version next to the current installation with the operator's package manager, anchors every staged dependency to the registry, and on install re-points the launcher symlink and exits through the normal graceful shutdown so the supervisor relaunches the new version. Restarts are refused while any volatile server-owned work exists, and unrecognized layouts fail closed with a visible reason.Background
The dogfood server is installed by the registry module (
bun add --ignore-scripts mux@nextunder/tmp/mux/npm, launcher symlink/tmp/mux/mux -> npm/node_modules/.bin/mux, nohup restart loop withRESTART_ON_KILL_VALUE=true). Until now the only way to pick up a newer build was a workspace rebuild:UpdateServicehad a null implementation in CLI mode and the About dialog said updates were desktop-only. The module's restart loop already acts as a crash supervisor, so an update only needs to stage a new version and exit cleanly.Implementation
src/node/services/serverUpdate/:installLayout.tsrecognizes the layout (launcher symlink resolving to the runningdist/cli/index.js, optionally through the publishedmuxforwarding shim; package manager from the text lockfile abovenode_modules; declared supervisor viaRESTART_ON_KILL_VALUE=trueorXUM_SERVER_SUPERVISED; HTTPS registry override).registry.tsfetches dist-tags and per-version manifests over an explicitly verifying undici agent withredirect: "error", and streams the release tarball to disk while checking its sha512 SRI.staging.tscreates a marked, uniquely named siblingxum-staging-<version>.<suffix>directory, installs the verified local tarball with the detected manager (--ignore-scripts, TLS validation forced), thenlockfile.tsre-verifies every dependency the manager recorded (bun.lock, package-lock.json, or pnpm-lock.yaml) against the digest the registry publishes over verified HTTPS, under the name each dependent requested (aliases and bundled flags are refused rather than trusted), andverifyStagedPackagerequires the published#!/usr/bin/env nodeentry, the executable bit, and a parse-onlynode --check.activation.tsswaps the launcher symlink atomically (temp link + rename);serverUpdater.tsis the state machine behind the existingupdate.*oRPC and keeps a verified stage installable across failed checks.ServiceContainer.collectRestartBlockers()composes existing signals (active streams, sessions with pending turn work, queued messages, or pending auto-retry, workspace lifecycle operations, in-process workflow runs, live desktop sessions, project mutations, open or starting terminals, running background processes, and any other in-flight RPC). Any blocker yields theinstall-blockedstatus and nothing happens.ServerService,WorkspaceService,TerminalService, andProjectServicelatchshuttingDownsynchronously; the oRPC in-flight middleware refuses new calls once shutdown begins, and config mutations run uninterruptible so an aborted call cannot leave a write untracked. The CLIcleanup()is reused as the restart callback.unsupported(and every action no-ops) on Windows, without a supervisor, when the process was not started through a launcher symlink, when the package manager lockfile is ambiguous or binary, or when the auth token is generated per process (a relaunch would lock every browser session out;MUX_SERVER_AUTH_TOKEN,--auth-token, or--no-authare required).NODE_TLS_REJECT_UNAUTHORIZEDandstrict-ssl=false; the manager child runs with TLS validation, lockfile, and optional-dependency flags and a stripped environment; every dependency is anchored to the registry after the install under the name its dependent requested, and every digest the lockfile records must be published (redirected metadata cannot rename a package, substitute the release tarball for one, or smuggle a second digest); staging directories carry an ownership marker so only this installation's stages are pruned, and their unique names cannot collide with foreign directories.window.api; it renders the unsupported reason, the blocker list, and an indeterminate download. Command palette actions cover check, download, install, and channel selection. Browser clients compareGET /versionwith the bundled build after reconnecting and reload when it changed.UpdateStatusSchemagainsunsupportedandinstall-blocked;downloading.percentis nullable.vscode/Makefileregenerates the rootsrc/version.tsthrough a phony target, so a direct extension build never bundles stale version metadata.docs/config/server-access.mdxgets an "Updating the server" section (requirements, registry and CA expectations, blockers, the module'smax_restart_attemptscaveat).Validation
MUX_BINARYdirect-entry bypass; both are refused as unsupported. Round 3 confirmed dependency re-verification, the stage marker, the strict shebang, and that an unrelatedxum-staging-*directory survives. Round 4 at the final head confirmed the unique stage name, that an empty foreign directory carrying the target version's name and a populatedxum-staging-*directory both survive Download and Install, dependency verification against registry.npmjs.org (about 10 s), and the idle install with exit code 0, supervisor relaunch, and browser auto-reload without an auth dialog.tests/ipc/update.test.tsconfirms the harness reportsunsupportedand the no-op path leaves the server running.Risks
@coder/xumreleases carry Sigstore provenance, so verifying that attestation against the release workflow is the natural follow-up (raised by Codex, deliberately deferred).bun addwould.Pains
nameunder the requested folder, pnpm keys packages by the served name and only its snapshot edges carry the requested one, bun records the requested name. Each was established with a local fake registry rather than from documentation.installCommandrecords what is forced and what merely fails closed.node_modulescheck meaningless; one review reply had to be corrected after a probe looked in the wrong place.Generated with
xum• Model:anthropic:claude-fable-5-1• Thinking:xhigh• Cost:$272.98