🤖 fix: allow server updates with recoverable watchers and newest npm builds - #4127
Conversation
|
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 Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 71762ab3d9
ℹ️ 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.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 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 Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a99389e1b
ℹ️ 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".
| { timeoutMs: SERVER_UPDATE_MONITOR_VERIFY_TIMEOUT_MS } | ||
| ); | ||
| if (result.kind === "ok") { | ||
| const records = new Map(result.value.flat().map((record) => [record.processId, record])); |
There was a problem hiding this comment.
Key registry rows by workspace and process
When a stale recovery row in one workspace shares a display-derived process ID with a current monitor in another workspace, and both workspaces also have live monitors, flattening all rows into a map keyed only by processId lets the stale row overwrite the current row depending on workspace iteration order. The ownership check then rejects the otherwise durable monitor, so background-processes continues blocking the server update until unrelated recovery completes; key the lookup by both owner workspace and process ID.
AGENTS.md reference: AGENTS.md:L10-L11
Useful? React with 👍 / 👎.
| process.monitor.pendingLines.length === 0 && | ||
| process.monitor.retainedMatches.length === 0 && | ||
| durableMonitorGenerations?.get(process.id) === process.monitor.armMetadata.createdAt | ||
| ) { | ||
| continue; |
There was a problem hiding this comment.
Do not exempt monitors that cannot be terminated
When a monitored process runs through an unreachable SSH/Coder runtime, list() treats a failed exit-code probe as still running and this condition exempts it solely because its local registry row is durable. During restart, RuntimeBackgroundHandle.terminate() also swallows remote command failures, after which the manager marks the process killed and clears it; the detached remote process therefore survives while recovery reports it as lost and may prompt the agent to launch a duplicate. Keep remotely unverifiable monitors blocking, or require confirmed termination before activating the update.
AGENTS.md reference: AGENTS.md:L10-L11
Useful? React with 👍 / 👎.
## 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
Server self-updates no longer refuse to restart because of background bash jobs whose monitors will be recovered after the restart, and the server gains an
npmupdate channel that follows the newest published package instead of dist-tags. A new integration harness drives the real install flow to prove the restart-blocker rules.Background
Server-mode updates (
update.install) take an idle snapshot before swapping the launcher. Any running background process counted as a blocker, so a workspace that had armed a long-lived monitored watcher (the normal agent pattern for dev servers and watch builds) could never update. Those watchers are safe to interrupt: their armed-monitor registration is durable, and after the restart the owning workspace receives a "Monitored background process lost" wake and can relaunch the job. Separately, the npm dist-tags used by the stable and nightly channels can lag a publication, so there was no way to follow the newest build.Implementation
ServiceContainer.refreshRestartBlockersnow asksWorkspaceService.getRestartSafeBashMonitorswhich running background monitors have a durable registry row for the same arm generation (samecreatedAt, not terminal, not lost). Only those are exempt inBackgroundProcessManager.getRestartBlockingProcessCount; foreground bash, stopped monitors, failed or stalled registry writes, and registry read failures or timeouts (1 s) all fail closed and keep blocking. The registry is read without draining pending writes on purpose: a stalled arm write must still block.npmchannel (server only):fetchNewestVersionpicks the newest exact version by publish time from the packument.UpdateService.getSupportedChannelsexposes what the current build supports; Electron reports stable/nightly only, maps a persistednpmpreference to stable, and rejects selecting it.update.getChannelreturns{ channel, supportedChannels }and the About dialog renders the toggle from that list.Validation
tests/ipc/serverUpdateRestartBlockers.test.tsruns the realServiceContainer, update IPC, bash tool spawn (never-matching monitor), PTY terminal, registry store, and product termination path: a terminal opened while blockers are being prepared still blocks the install (rawinstall-blockedpayload lists onlyterminals), closing it makes a restart-free retry succeed; a job whose registry write failed blocks (background-processes), a product-driven stop clears it, a fresh durable monitor is exempt, and a second container over the same root schedules recovery for the surviving row. Each of four production mutations (drop terminal count, count every process, trust in-memory monitors, skip the refresh) turns the harness red.Risks
Restart gating is the safety boundary for self-updates. The change only narrows blocking for one precisely identified class (running, non-foreground, armed and unstopped monitor with a matching durable row); every uncertainty path keeps blocking. The npm channel can select pre-releases by design; it is opt-in and server-only.
Generated with
xum• Model:anthropic:claude-fable-5-1• Thinking:xhigh• Cost:$12.56