Tags: coder/xum
Tags
🤖 fix: report backup repository access denial instead of a login fail… …ure (#4226) ## Summary When the backup repository refuses a credential the host already accepted, the Settings > Backup section now says so, naming the refused credential and quoting the remote's own reason, instead of telling the user to run `gh auth login`. ## Background With a wrapper-managed `gh` (for example on a Coder workspace), `gh auth status` succeeds, but pushing to a backup repository the token cannot write returns `remote: Write access to repository not granted.` followed by `The requested URL returned error: 403`. `runGitWithCredentialLadder` matched that as an authentication failure and threw the fixed message "Could not authenticate to the backup repository. Check your SSH key or `gh auth login`", sending a logged-in user to log in again. The thrown `cause` was also only the ambient rung's failure, so the gh rung's informative denial disappeared whenever the ambient rung failed differently (for example "could not read Username ... terminal prompts disabled"). ## Implementation - `credentials.ts`: `ACCESS_DENIED_PATTERN` (403, "Write access to repository not granted", "Permission to X denied", "Repository not found", read-only deploy key) selects, among all recorded rung failures in ladder order, the first the remote recognised and refused. The diagnostic line that matched the classifier (a `remote:` or `ERROR:` line, else the `fatal:` line) and the credential label go into the message. Pure login failures keep the existing message. The error code stays `AUTH_FAILED`, so there is no IPC or schema change and the `instanceof` checks in `gitRepo.ts` are unchanged. - `src/constants/backup.ts`: shared `BACKUP_CREDENTIAL_LABELS`, used by the backend message and by `BackupSection.tsx` (replaces the local `getCredentialLabel`). - Tests: three new behavioral cases in `credentials.test.ts` (a 403 denial names the gh rung and quotes the reason; the denying rung wins over a credential-less ambient rung; an ssh `ERROR: Permission to ... denied` names the ssh rung) plus a login-guidance assertion on the existing exhausted-ladder case. ## Validation - Remote dogfood UAT (Coder Agents) on this exact head: 403 denial on a read-only public repository via "Back up now", 404 "Repository not found" via Validate, the login message with no credential available at all (credential-stripped server), the network error for an unreachable host, a successful backup to a writable repository, rendering at 1280 px and 375 px, and the ssh rung denial once known_hosts was populated. All passed. - Red-green: disabling the access-denied selection fails exactly the three new tests. ## Risks Low. Classification only changes which message is thrown after every rung has already failed; ladder order and retry behaviour are unchanged. The one widening is that a read-only deploy key ("marked as read only") now counts as an auth failure, so the ambient rung gets a turn instead of the raw git error surfacing. ## Follow-up (pre-existing, out of scope) UAT also found that an ssh host-key verification failure (`Host key verification failed`, here from `coder gitssh` without a known_hosts entry) still surfaces raw multi-line stderr in the error area. That path matched no classifier before this PR either and is untouched here; it deserves its own classifier and message. <details> <summary>Delivery record (reviews, UAT, CI)</summary> **Reviews** (process limit: 6 completed code, security, and advisory reviews per PR; 7 completed, see note) | # | Kind | Head | Reviewer | Findings | Disposition | |---|------|------|----------|----------|-------------| | 1 | Code (automatic) | `42c9038c6` | Codex ([request](#4226 (comment)), [verdict](#4226 (comment))) | none ("Didn't find any major issues", reviewed commit 42c9038) | clean, no threads | | 2 | Security (automatic) | `42c9038c6` | Codex ([result](#4226 (comment))) | none | clean | | 3 | Advisory (fresh clean-context final reviewer) | `42c9038c6` | Xum read-only review sub-agent | P2: `remoteReason()` quoted the first `remote:` line, which could be server progress rather than the denial. P3: a bare HTTP 403 treated as proof that re-authenticating cannot help. Recommendation: specific blocker on the P2. | P2 fixed in `1adb0ed5d` (quote the remote line that matched the classifier; new test; red-green verified). P3 was first rejected with reasoning (401 vs 403), then fixed in `a56ddd6a6` after Codex raised the same point: the "signing in again will not help" sentence is gone. | | 4 | Code (automatic) | `1adb0ed5d` | Codex ([request](#4226 (comment)), [review](#4226 (review))) | P2 [thread](#4226 (comment)): a progress `remote:` line could be quoted over a matching `fatal:` 403 line. P2 [thread](#4226 (comment)): bare 403 / not-found treated as proof the credential was accepted. | Both fixed in `a56ddd6a6` (search remote and fatal lines for the classifier match; sentence removed); replied on both threads and resolved them. | | 5 | Security (automatic) | `1adb0ed5d` | Codex ([result](#4226 (comment))) | none | clean | | 6 | Code (automatic, "New commits" trigger, not requested) | `a56ddd6a6` | Codex (summary row completed 02:16:19Z; 👍 reaction 02:17:16Z) | none | clean, no threads | | 7 | Security (automatic, "New commits" trigger, not requested) | `a56ddd6a6` | Codex (summary row completed 02:17:11Z) | none | clean | Process note: the six-review limit was exceeded by one. This repository's Codex integration starts a code and a security review automatically on every push ("New commits" trigger); rows 6 and 7 were not requested and cannot be suppressed without changing repository settings, which was out of bounds. No further review was requested after row 5. **Remote dogfood UAT** (Coder Agents, vibe-coding template; evidence kept in the driving workspace under `.mux-uat/`, not committed) - Round 1 on `42c9038c6` (remote chat `c742c3f5`): the remote agent's own verdict was FAIL, twice. A separate critical UAT runner re-examined the evidence, rejected duplicate and cropped screenshots, forced re-proofs, and endorsed PASS for every claim in scope: 403 denial via "Back up now", 404 "Repository not found" via Validate, login message with no credential available, network error for an unreachable host, successful backup to a writable repository, rendering at 1280 px and 375 px, ssh-rung denial after populating known_hosts. The FAIL came from one pre-existing defect outside this change (below). - Smoke on `1adb0ed5d`: PASS, superseded by the next push. - Smoke-final on `a56ddd6a6` (same chat, remote `git rev-parse HEAD` and the app version badge both confirm the SHA): PASS. 403 via "Back up now" and 404 via Validate both render the final wording; DOM checks confirm the removed sentence and `gh auth login` are absent. The remote checkout reported `-dirty` because its bun 1.2.15 rewrote `bun.lock` (repo pins bun 1.3.5): the diff is a `configVersion` header plus re-hoisting of the type-only packages `@types/node` and `undici-types`; no runtime package changed and nothing under `src/` differed, so the tested build is dependency-equivalent to the committed lockfile. **Deferred, pre-existing:** an ssh host-key verification failure (`Host key verification failed`, here from `coder gitssh` without a known_hosts entry) surfaces raw multi-line stderr in the Backup error area. It matched no classifier before this PR and is untouched by it. Owner: next backup follow-up PR. Trigger: add a host-key classifier and message in `credentials.ts` next to the network and login classifiers. **CI:** on `42c9038c6`, attempt 1 failed on "Codex Comments" (ran before Codex answered) and "Test / Unit" (Bun 1.3.5 segfault, zero failing tests). On `a56ddd6a6`, attempt 1 failed only on "Codex Comments" for the same timing reason; the failed jobs were rerun once Codex had completed. </details> --- _Generated with `xum` • Model: `anthropic:claude-fable-5-1` • Thinking: `xhigh`_ <!-- xum-attribution: model=anthropic:claude-fable-5-1 thinking=xhigh -->
🤖 feat: manage installed plugin component selections (#4213) ## Summary Replace installed-plugin **Add components** with **Manage components**. Add or remove imported skills and MCP servers, including clearing both groups, without uninstalling the plugin or deleting its files, data, or saved workspace MCP preferences. ## Implementation 1. Replace the internal additive API with an atomic `setComponents` operation. Validate the reviewed Git SHA, full-tree content receipt, and nullable raw selection baseline before writing the desired set. Preserve legacy import-all behavior on no-op. 2. Use the existing managed registry as MCP component-policy authority. Gate held tools and actual stdio/HTTP/SSE startup under the existing admission locks, including retries/fallback. Retire only removed clients; retained plugin and unrelated clients remain connected. 3. Persist before runtime reconciliation. No-op saves do nothing. Failed cleanup reports a saved-state warning and remains retryable. Re-adding a server honors its saved workspace opt-in; new imports remain disabled until opted in. 4. Add editable imported checkboxes, group selection controls, pending-change counts, empty saves, cancellation, and conflict/lost-response recovery. Refresh both editor and card counts after a stale save is rejected. Update the palette, full-App stories, and existing user documentation. ## Validation Verified on **`f2609e183f50f3a11af1215ec4342f9b45a74667`**: - **930 unit tests passed**, one Windows-only local skip, across 25 affected/sibling suites. **15 real-IPC/full-App UI tests** passed. - `make static-check`, `make static-check-full`, and `git diff --check` passed on this head. The independent final code review found no concrete blocker. Current-head external CI and code/security reviews remain separate merge gates. - Test-first regressions exercise actual stdio/HTTP/SSE/fallback launch admission, delayed launch/lock release, live MCP admission during blocked inventory hashing, complete real uninstall/reinstall with identical receipt bytes, and pending installer journals. Read-only inventory uses the existing journal/epoch bracket; save-time SHA/content/selection validation remains locked. - UI regressions cover an acknowledged save followed by failed inventory confirmation, and two superseding-writer schedules. Counts/current checkboxes refresh, conflicts do not show success, and nothing is automatically resubmitted. - Recorded isolated, provider-free desktop and **375×812** UAT verified removal, mixed re-add, current counts, open-panel MCP availability, and stale-tab rejection. Keeper PID **353850** survived throughout; echo **353844** disappeared on removal and was replaced only on re-add by **383254**. Stale rejection changed neither PID nor registry, workspace MCP preferences, data, or observed source hashes. Mobile document width stayed **375**. - Desktop/mobile recordings and **six full-App Storybook interactions** cover `668094672`; UI code is unchanged on this head. Recorded desktop **240.3s** and mobile **121.0s** final frames were checked. Deterministic failure-injection races are automated-test evidence, not claimed as manual reproductions. - Six test-first startup-close regressions cover initial, additive, retry, leased restart, retired-only, and re-add publication. A rejected close remains tracked by object identity without active tools/stats; existing reconciliation, idle, or prefix cleanup retries it successfully. Retained clients survive and re-addition uses a fresh client. ### Current-head startup-cleanup regression evidence Recorded the actual six-case failure-injection test run on `f2609e183`: **6 passed, 0 failed**, exit **0**. The accelerated recording is **5.0s**; its final frame was checked. This is recorded automated regression evidence, not a manually induced process-close failure.  Recorded startup-cleanup regression run: https://github.com/user-attachments/assets/0638127c-0a9c-49e0-9e10-66a8243d5b23 ### Desktop/mobile UAT (`668094672`; UI unchanged)   Desktop removal, mixed re-add, and open-panel runtime observations: https://github.com/user-attachments/assets/61ee6abf-2176-490f-a71d-82b4cd30c877 Mobile stale-save rejection and refresh: https://github.com/user-attachments/assets/dafb7541-b497-4712-8842-808d1182a0d0 <details> <summary>Earlier implementation evidence</summary> ### Earlier validation - Local affected unit and sibling suites on the rebased head: **717 passed, one platform skip**. - Real IPC/full-App UI: **10 passed**. - Full-App Storybook interactions: **six passed** after warming the server. Fresh-server navigation initially exceeded the default timeout; unchanged warm runs passed. - `make static-check`, `make static-check-full`, and `git diff --check` passed again on the rebased publication head before push. - Recorded isolated, provider-free UAT covered desktop and 375px layouts, keyboard operation, add/remove/re-add, no-op, Cancel, empty selection, persistence, and two-tab conflicts. - Actual process evidence: keeper PID **3152939** survived echo removal and re-add. On the corrected build, keeper PID **3413952** survived the competing removal/re-add; rejecting the stale save changed neither PID nor registry/configuration bytes. - Recordings show the implemented `52046f230` build. Its feature commits were rebased without conflicts onto current main before publication; the feature patch is unchanged. ### Screenshots  <details> <summary>Desktop and mobile conflict recovery</summary>   </details> The attached videos cover selective removal/re-add, Cancel/empty selection, and corrected mobile stale-save recovery. ### Earlier recordings https://github.com/user-attachments/assets/2bb77867-30e7-4bf5-bbd2-e5507d30deb9 https://github.com/user-attachments/assets/655c314a-d73a-41ea-b888-6fc5479932c5 https://github.com/user-attachments/assets/61bccb75-17b4-4da1-ac27-10ca33fc0679 </details> ## Runtime boundary Only skills and MCP component selection changes. Initial install, update, uninstall, agents, hooks, workflows, and slash commands retain their existing behavior. Calls already admitted before the selection commit may finish normally. Idle sibling processes reconcile at their next MCP boundary; immediate cross-process process termination is not promised. **Regression risk:** Managed MCP admission and cleanup are the sensitive paths. Real mutations can temporarily deny admission; read-only inventory must not. Deterministic transport/installer races and retained-process UAT cover those boundaries. No new persisted fields, journal files, or locking subsystem are introduced by these review fixes. --- <details> <summary>📋 Implementation Plan</summary> The plan below is the accepted implementation snapshot. Publication and conditional merge-queue submission were authorized afterward. # Reversible installed-plugin component management ## Goal and scope Replace the installed-plugin **Add components** panel with **Manage components**. Users can add components or deselect previously imported skills and MCP servers, then explicitly save the new selection. Assumption: removing a component means removing it from Xum's available plugin components, not deleting the plugin's installed source files. Keeping the plugin installed makes re-adding possible without fetching again. Empty selections are valid. Initial installation remains unchanged. Scope matches the existing chooser: skills and MCP servers only; agents, hooks, workflows, and slash commands are unchanged. Preserve workspace MCP enablement, tool allowlists, identity, and data. New MCP imports remain disabled without a workspace opt-in. Re-adding a previously enabled server can restore that saved opt-in; the panel must say so. ## Verified foundations - `PluginsSettingsSection.tsx`: `ComponentChooser` locks imported checkboxes; `AddComponentsPanel` models additions only. - `agentPlugins/installService.ts`: `addComponents` unions selections under `runExclusive`; the registry already supports explicit empty allowlists. - `agentPluginInstalls.ts`: absent `importedComponents` means legacy import-all. Cancel/no-op must preserve that distinction. - `getComponents` returns the installed SHA, full-tree content hash, inventory, and current imported selection. SHA/hash do not change when another panel changes only the registry selection. - Skill discovery and `loadPluginMcpServers` already filter by the selected allowlist. Active MCP connections require additional targeted reconciliation; blanket plugin teardown would disrupt retained servers. ## User experience 1. Rename the installed-plugin action to **Manage components**. Keep the existing inline panel; no new settings page or modal. 2. Initialize checkboxes from the current imported set. All available skills and MCP servers are toggleable. **Select all** and **Clear** operate on the whole group. 3. Show one concise pending-change summary, such as “Add 1 skill · Remove 1 MCP server.” Use **Save changes** and **Cancel**. Disable Save only while busy or unchanged—not when the selection is empty. 4. Saving updates the card counts. Cancel discards the draft. Reopening loads current state. An empty selection explains that no skills or MCP servers are imported, while the plugin remains installed. **Uninstall** remains separate. 5. Explain that deselection preserves source files, data, and workspace MCP preferences. New imports need workspace enablement; re-added servers retain previous preferences. ## Implementation sequence Deliver two dependent, reviewable increments. Stabilize increment 1 before exposing removal in increment 2. No PR creation or publishing is authorized by this plan. ### 1. Make MCP component removal safe at runtime **Estimated net product code: +180–300 LoC.** No UI changes in this prerequisite. Files: `src/node/services/mcpServerManager.ts`, `src/node/services/agentPlugins/registry.ts`, `src/node/services/agentPlugins/mcpConfig.ts`, and `CoreServicesRootLive` in `src/node/services/di/layers/core.ts` (the existing `pluginInvalidation` wiring). 1. Write failing runtime tests first. Cover removal with a retained sibling and unrelated server, held tools in a second manager sharing the registry, pending startups, active leases, and simultaneous add/remove with equal counts. 2. Use a canonical component-policy snapshot from the existing atomic `plugins.json` as the cross-process authority. Include managed installation identity and selected MCP names. Extend the existing stable-scan/call-time invalidation seam; do not create another epoch file, use mtime-only checks, or bump the global tree epoch. 3. Recheck that policy at tool authorization and across config discovery/startup publication. On a policy change, reconcile against fresh configuration and retry stale publication. Carry managed-policy provenance from discovery; use the unqualified `info.plugin.serverName`, not the composite key, for allowlist membership. Reuse existing managed-home/alias identity rules. A missing registry entry must not reclassify an already managed server as unmanaged. Unreadable managed policy fails closed without restricting genuinely unmanaged plugins. Cover those boundaries in tests. 4. Extend the existing additive-retention path to handle plugin-selection removals and mixed additions/removals when retained configs are unchanged. Compare sets, not counts. Reuse `computeSignatureEntries`; do not manually patch signature strings. Leave existing behavior for unrelated configuration changes intact. 5. Revoke removed keys from tool authorization, availability, and retry queues. Fence in-flight startup publication. Retire only removed clients, respecting existing lease cleanup. Retained plugin and unrelated clients must stay connected. Never mark a deselected server for retry. **Runtime contract:** An invocation whose call-time authorization check occurs after the selection commit must reject a deselected server, even through an old tool object in another process. “Admitted” means an actual invocation passed that gate—not that its tool definition was advertised. Already-admitted calls may finish or fail normally. Existing prompts cannot be retroactively edited. Idle sibling processes reconcile at their next MCP boundary; immediate cross-process process termination is not promised. Keep registry reads within the existing bounded gate and check their latency in the targeted runtime tests. **Gate:** The targeted MCP/registry/discovery tests pass. Prove retained connection identity, not merely that a new connection works. No API for MCP removal ships before these checks are green. ### 2. Ship replacement selection and the management panel **Estimated net product code: +90–170 LoC.** Depends on increment 1. Includes backend, UI, tests, and updated user docs. 1. **Write behavioral tests first.** Exercise subset replacement, mixed add/remove, empty selection, cancel/no-op, legacy import-all, invalid component names, stale selection, same-SHA file edits, and failed persistence. 2. **Replace the internal additive API with `agentPlugins.setComponents`.** Update `src/common/orpc/schemas/api.ts`, `src/node/orpc/router.ts`, and `src/node/services/agentPlugins/installService.ts`. Frontend/backend are version-synchronized, so do not retain an unused additive compatibility endpoint. - Input: plugin name, `expectedLockedSha`, `expectedContentHash`, required nullable `expectedImportedComponents`, and desired `importedComponents`. - `null` baseline means the legacy absent field. Compare selections as normalized sets; preserve absent versus explicit empty. - Under `runExclusive`, read strict registry state, check all three baselines, validate desired names against the verified installed inventory, and atomically replace only `importedComponents`. - Reuse full-tree content-consent checks, including verification around inventory reads. No remote fetch, tree rewrite, data deletion, identity change, override pruning, or migration. - Preserve legacy import-all on no-op. A changed legacy selection becomes an explicit allowlist. Later plugin updates preserve that selection and do not auto-import new components. 3. **Publish disk state before runtime effects.** After persistence and release of the mutation lock, invalidate skill availability and reconcile local MCP managers through increment 1. Reconciliation reads current policy rather than replaying a stale removal list, so rapid remove→re-add cannot close a newly valid client. A failed write changes neither selection nor runtime. If cleanup fails after persistence, do not roll back or pretend nothing saved: show the saved selection, surface a cleanup warning, and retry retirement at the next reconciliation. Authoritative call-time gating must still deny removed tools. Keep any result-envelope extension local to this operation. 4. **Update the UI in `PluginsSettingsSection.tsx`.** Rename `AddComponentsPanel`, use a full-selection draft, unlock imported rows, and implement the interaction above. Keep the shared initial-install chooser behavior intact. Capture the raw baseline separately from the displayed effective selection. - Refetch after success; clear pending state on close/reopen. - On a stale SHA/content/selection conflict, refresh inventory and baseline, reset the draft, and require a new explicit Save. Never auto-resubmit a stale choice. - On ordinary failure, retain the draft and show an actionable error. If a response is lost after a possible commit, refetch before describing the persisted result. 5. **Update access paths and fixtures.** Rename the command-palette label and matching intent in `src/browser/utils/commands/sources.ts`, `pluginsSectionIntents.ts`, and their command-ID definition/callsites. Preserve Tab/Space/Enter operation. Update `src/browser/stories/mocks/orpc.ts` and `src/browser/stories/App.pluginImports.stories.tsx` to use replacement semantics. 6. **Update existing docs.** In `docs/agents/agent-skills.mdx`, replace the one-way-import limitation. Explain deselection, preserved data/preferences, empty selections, and re-add behavior. Do not add a new documentation page. **Gate:** Backend, real-IPC UI tests, full-App Storybook interactions, and static checks pass. Opening the panel without edits must not mutate the registry. Initial installation, update, and uninstall regression tests remain green. ## Acceptance criteria 1. Users can add and remove imported skills/MCP servers in one panel, including removing all of them without uninstalling. 2. Save persists exactly the chosen set. Cancel and no-op leave disk and runtime untouched. Reopening and restarting show the saved choice. 3. Deselected skills disappear from fresh skill discovery. Deselected MCP tools are unavailable, including through stale held tools; retained servers are not restarted. 4. Installed files, instance identity, data, saved MCP preferences, and unrelated plugins are unchanged. Newly added MCP servers do not gain unsolicited workspace enablement. 5. Concurrent edits and changed installed content cannot silently overwrite the user's reviewed selection. Invalid input and write failure leave the prior state intact. 6. Desktop and narrow layouts support keyboard use without horizontal overflow. Initial install/update/uninstall retain their current behavior. ## Validation and dogfooding ### Automated gates Run `src/**` suites with Bun. Run `tests/**` suites with Jest. Include sibling suites for every touched production module. ```bash bun test src/node/services/agentPlugins/installService.test.ts \ src/node/services/agentPlugins/registry.test.ts \ src/node/services/agentPlugins/discovery.test.ts \ src/node/services/agentPlugins/mcpConfig.test.ts \ src/node/services/mcpServerManager.test.ts \ src/node/services/agentSkills/agentSkillsService.test.ts TEST_INTEGRATION=1 bun x jest tests/ipc/agentPlugins.test.ts \ tests/ui/config/pluginImports.test.ts --runInBand # Start a persistent Storybook server with make storybook, then: bun x test-storybook --url http://127.0.0.1:6006 \ --maxWorkers 1 App.pluginImports.stories # Run after tests, not concurrently with temporary test-module creation. make static-check make static-check-full ``` Extend full-App stories only. Cover saved removals, empty selection, and conflict recovery. Pin the phone story with `parameters.pixel.matrix.viewports: ["phone"]` and matching `globals.viewport`. Keep breakpoint assertions valid in the desktop-sized Storybook test runner by using the existing fixed-width/guarded pattern. ### Hands-on walkthrough 1. Create a disposable local Git plugin fixture outside the checkout, following `installService.test.ts` fixture patterns. Include two skills and two harmless local MCP servers with observable process identities. Add a separate temporary workspace/project. No provider credentials are needed. 2. Start an isolated server on free ports: ```bash make dev-server-sandbox \ DEV_SERVER_SANDBOX_ARGS="--clean-providers --clean-projects" ``` Use the printed URL/root. Enable Agent Plugins in that sandbox. Install the local fixture through **Add plugin** using its absolute Git path. Do not seed or edit the personal Xum root. 3. Load `agent-browser skills get core`, open the sandbox, and start a video recording before interaction. Use `snapshot -i` for functional checks. Import a subset; enable one imported MCP server in the test workspace. 4. Manage the plugin: add another component, deselect an existing one, save, reopen, and reload. Verify counts and actual skill/MCP availability. Verify the retained MCP process did not restart. Re-add the previously enabled server and verify the documented saved-preference behavior. 5. Test Cancel, no-op, Clear both groups, and re-add after an empty selection. Open two panels in separate browser tabs; save one, then verify the other's stale save is rejected and refreshed. 6. Repeat at desktop and approximately 375px width. Use the Storybook manager's pinned phone variant as the separate visual gate. Capture screenshots of pending removals, the saved/empty state, and phone layout. Stop the recording and use `attach_file` for screenshots **and video**. 7. Stop only owned sandbox/browser/Storybook processes. If GitHub publication is later requested, upload the evidence with `gh ... --attach`; otherwise keep delivery local. **Final gate:** Run the complete relevant tests and static checks on the final code. Get one clean-context independent readiness recommendation from the diff, tests, and dogfood evidence. Record the verdict and any concrete blocker. Do not claim implementation success if required checks or evidence are missing. <details> <summary>Planning review disposition</summary> One independent review accepted the UI/API design and recommended reducing runtime scope. The plan limits reconciliation to plugin-selection changes and makes runtime tests a prerequisite. Retain cross-process checks: process-local invalidation alone misses held tools in sibling processes. Verified existing call gates already await disk-backed epoch/override readers; extend that bounded seam rather than introducing a separate synchronization system. Planning left the source tree clean. Implementation and test execution remain pending. </details> ## Total size and exclusions **Recommended approach: approximately +270–470 net product LoC**, excluding tests, stories, and docs. This includes the runtime protections; merely unlocking checkboxes is not sufficient. Re-estimate at the first gate if the existing reconciliation seam cannot support the bounded change. No new component types, plugin-file deletion, workspace-setting cleanup, generic plugin lifecycle framework, or unrelated MCP refactor. Planning is read-only; the commands above are implementation-time gates, not tests already run. </details> --- _Generated with [`xum`](https://github.com/coder/xum) • Model: `coder:openai/gpt-6-astra` • Thinking: `xhigh` • Cost: `$264.74`_ <!-- mux-attribution: model=coder:openai/gpt-6-astra thinking=xhigh costs=264.74 -->
🤖 release: v0.28.5 (#4175) ## Summary Version bump for the v0.28.5 patch release. Headline changes since v0.28.4: remote server connections in the desktop app (#4101), self-updating `xum server` under a restart supervisor (#4083, #4127), first-class GPT-6 Astra and Astra Pro support including Codex OAuth routing (#4064, #4094, #4106, #4124), token-budget context window rollovers (#4097), the workspace remembering model and mode on send (#3968), in-place plugin updates (#4164), the optional flat sidebar chat list (#3994), and copying selected chat text as Markdown (#4170). It also carries a long run of streaming, compaction, and task-lifecycle fixes (reconnect streaming #4123, message edits during active streams #4153, Codex OAuth prompt-cache routing #4159, compaction/history fencing #4133 through #4148, task lock ordering #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 -->
🤖 release: v0.28.4 (#4063) ## Summary Version bump for the v0.28.4 patch release. The headline change since v0.28.3 is Gemini 3.8 Flash becoming the default Gemini Flash model (#4060). The release also carries browser Login with Coder on remote Xum servers (#4047), the opt-in project bundle for settings backup (#4043), the connection-indicator slow-response surfacing (#4059), send-queue and terminal-wake fixes (#4053, #4052), and the Effect Phase 11 runtime refactors. ## Implementation Bumped with `node ./scripts/set-package-version.js 0.28.4` so the root `package.json` and the legacy `packages/mux-compat` forwarding package stay version-locked (the v0.28.3 bump missed the compat package and broke `Test / Unit` on main, fixed in #4048). `src/common/compat/productIdentity.test.ts` passes locally. After this PR merges, the `v0.28.4` 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: `medium` • Cost: `$0.00`_ <!-- mux-attribution: model=anthropic:claude-fable-5-1 thinking=medium costs=0.00 -->
🤖 release: v0.28.1 (#3751) ## Summary Bump version to 0.28.1 for the next stable release. ## Background 35 commits landed since v0.28.0, headlined by Claude Opus 5 and native Kimi K3 support (new Moonshot AI provider), Gemini 3.6 Flash, project-less scratch chats, a FIFO message queue behind special sends, sub-agent reports in chat, sticky sub-agents, arbitrary file staging from chat and the creation composer, and a skills refresh. After merge: tag the squash commit as `v0.28.1` and publish the GitHub Release, which triggers the desktop, Docker, VS Code, and npm release pipelines. --- _Generated with `mux` • Model: `anthropic:claude-fable-5` • Thinking: `xhigh` • Cost: `$0.00`_ <!-- mux-attribution: model=anthropic:claude-fable-5 thinking=xhigh costs=0.00 -->
🤖 fix: stamp real version in Nix flake builds (#3686) ## Summary Nix flake builds displayed `unknown` as the app version (top-left title bar and About dialog) because the version stamp could never be computed inside the build sandbox. This feeds the revision the flake already knows into the build so the version is stamped correctly. ## Background The version shown in the UI comes from `src/version.ts`, which is generated at build time by `scripts/generate-version.sh` using `git describe --tags --always --dirty` and `git rev-parse --short HEAD`. Both fall back to the literal string `"unknown"` when the git commands fail. Nix copies only git-tracked files into the build sandbox and strips the `.git` directory, so those git commands have no repository to read and both fall back to `"unknown"`. Having `git` in `nativeBuildInputs` only provides the binary, not the repo metadata, so it doesn't help. Ironically the flake already computes the revision (`version = self.rev or self.dirtyRev or "dev"`) but never passed it into the build, so that value was discarded before the script ran. ## Implementation The version script already supports a `RELEASE_TAG` override that skips the git calls and sets `git_describe` directly (used by CI release builds). The flake's `buildPhase` now exports that revision: - `RELEASE_TAG="${version}"` sets `git_describe`. - `GIT_COMMIT="${builtins.substring 0 12 version}"` sets the commit field via a new override. `generate-version.sh` now honors an incoming `GIT_COMMIT` env var, keeping the git-based default when it's unset so non-Nix builds are unchanged. ## Risks Low. The change only affects build-time version stamping. Non-Nix builds keep their existing git-based behavior (the `GIT_COMMIT` default is unchanged when the env var is unset), and the script's `unknown` fallback still applies when neither git nor overrides are available. One cosmetic note: Nix dev builds now log `Release build: using RELEASE_TAG=...` in build output, which is slightly misleading but harmless. --- _Generated with `mux` • Model: `anthropic:claude-opus-4-8` • Thinking: `xhigh` • Cost: `$1.11`_ <!-- mux-attribution: model=anthropic:claude-opus-4-8 thinking=xhigh costs=1.11 -->
PreviousNext