Skip to content

🤖 feat: Agent Plugins install/update UX (managed installs, v1) - #3820

Open
ThomasK33 wants to merge 44 commits into
mainfrom
agent-plugin-install-ux
Open

🤖 feat: Agent Plugins install/update UX (managed installs, v1)#3820
ThomasK33 wants to merge 44 commits into
mainfrom
agent-plugin-install-ux

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

v1 of the Agent Plugins install/update UX ("Option B: managed installs"): paste a git URL or owner/repo[@ref] into Settings → Plugins, get a consent preview of everything the plugin contributes (manifest, every skill, every MCP server command line), and install into ~/.mux/plugins with provenance recorded in a managed-install registry. Update badge + manual update, uninstall with override pruning, all behind the existing agent-plugins experiment.

Background

PR #3815 shipped Agent Plugins 1.0.0 as discovery-only: users had to git clone into container dirs by hand, with no provenance, no update signal, no uninstall, and no list surface. The design doc (docs/research/agent-plugin-integration-options.md on branch research-agent-plugin-ux) compared five options; Thomas signed off on Option B (managed installs) with the §6 proposed decisions ratified.

Approved decisions implemented here

  1. Registry — a standalone ~/.mux/plugins.json owned by the install service (atomic, throwing writes; in-process serialized mutations). Lenient-on-read: invalid entries are dropped with a warning, and plugin names are pattern-validated so a malformed entry can never resolve a path outside the container. (Deviation from §6-Q2's letter, following its own contingency: Codex review demonstrated that .passthrough() only affects schema validation — older builds rebuild config.json from known fields on save, so a downgrade would drop an embedded registry section. Q2 priced exactly this: "Cost if wrong: a one-time migration to a separate file." A file older builds never rewrite is the only mechanism that actually survives downgrade round-trips, and owning the write path also makes registry-persistence failures observable for rollback.)
  2. Tracking semanticssource.ref is the tracking channel, lockedSha is what runs. No ref given ⇒ record the remote default branch + pin its current SHA. Tag/SHA refs are pinned (moved tags surface a tag moved warning badge). Nothing auto-applies, ever.
  3. Consent preview — temp shallow clone to ~/.mux/plugin-staging (never inside a discovery container), validated with the same validatePluginManifest + discovery code the runtime uses, listing manifest metadata, every skill name+description, and every MCP command line (rendered against the final install path, incl. PLUGIN_DATA expansion). Cancelling writes nothing — the preview is stateless; install re-fetches the exact consented SHA and fails loudly if the remote moved.
  4. Update — badge + manual only; checks run on Settings-section open and on the explicit button (git ls-remote vs lockedSha, no fetch, no timers). Applying = temp clone at the new SHA → re-validate → wholesale directory swap (rename-old → promote-new → delete-old, with rollback) → bump lockedSharecycle that plugin's running MCP servers via the new MCPServerManager.stopServersWithKeyPrefix (content can change behind an unchanged stdio command line, so the config-signature check cannot notice). Local edits to a managed plugin dir are discarded on update (documented).
  5. Uninstall — deletes plugin dir + registry entry + prunes that plugin's plugin:<instanceId>:* keys from every local workspace's MCP overrides (reinstall re-attaches the same instanceId, so stale overrides would silently re-enable servers). ~/.mux/plugin-data/<instanceId> is preserved behind an "also delete stored plugin data" checkbox, unchecked by default.
  6. Scope — global-only; the installer never writes into a project checkout.
  7. Human-only surfaces — Settings section + palette commands (Settings: Plugins, Install Agent Plugin…, Check for Plugin Updates, Update All Plugins — keyboard rule). No agent-facing installer tool.
  8. Name collisions — existing registry entry or target dir ⇒ clear error; the installer never overwrites.
  9. Subpath grammar, not subpath installsowner/repo/sub/path[@ref] parses and the subpath field is persisted in the source descriptor, but installs reject with "monorepo subpath installs land in v2". Claude Code plugin/marketplace repos fail with a clear message naming the limitation (source stays a discriminated union so an import adapter is additive).

Implementation

  • Step 1 — registry schema: src/common/config/schemas/agentPluginInstalls.ts (entry + tagged-union source + plugins.json file schema); name grammar shared with the manifest validator via src/common/utils/agentPluginName.ts.
  • Step 2 — service + oRPC: discoverAgentPluginAt (public single-root wrapper over the existing per-entry discovery, so staged clones get the exact runtime validation); normalizeRepoUrlForClone extracted to src/node/utils/gitUrls.ts (shared with the project clone flow); sourceInput.ts grammar; AgentPluginInstallService (preview/install/list/uninstall/checkUpdates/update, mutations serialized on an internal queue, staging under ~/.mux/plugin-staging with stale-dir reclamation, GIT_TERMINAL_PROMPT=0 + SSH BatchMode so private repos without auth fail fast instead of hanging); plugins.* oRPC namespace returning Result values; MCPServerManager.stopServersWithKeyPrefix recycle hook. Backend gating mirrors the MCP provider: the service is constructed with isEnabled: () => experimentsService.isExperimentEnabled(AGENT_PLUGINS).
  • Step 3 — UI: PluginsSettingsSection (list with unmanaged/missing/update available/tag moved/pinned badges, two-phase add flow, inline uninstall confirm), experiment-gated section registration + redirect + palette entry.
  • Step 4/5 — docs, stories, tests: docs additions in docs/config/mcp-servers.mdx + docs/agents/agent-skills.mdx; Storybook stories with play assertions (consent preview, update states, unchecked-by-default checkbox); unit tests for the input grammar, registry round-trip/self-heal, and the full service lifecycle against real local git remotes (hermetic — local-path remotes exercise the same clone/ls-remote plumbing).

Validation

  • make static-check green (typecheck, ESLint, prettier, docs links); targeted suites: 393 tests across the touched areas (agentPlugins, config, schemas, SettingsPage, palette sources, MCPServerManager, oRPC router, projectService) all pass; test-storybook passes for the new stories.
  • Live dogfooding in a make dev-server-sandbox instance (screenshots in the workspace transcript): enabled the experiment via Settings → Experiments (Plugins section appeared immediately), installed a local fixture repo through the full preview → consent → install flow, verified the on-disk registry entry + plugin tree (no .git), advanced the fixture remote → update available badge appeared on "Check for updates" → Update bumped lockedSha/version/updatedAt, uninstall (checkbox unchecked) removed dir + registry but preserved plugin-data, and the reinstalled plugin's MCP server surfaced in Settings → MCP as plugin · … default-disabled/read-only.

Risks

  • Config surface: none — the registry is a standalone ~/.mux/plugins.json; config.json load/save is untouched. Malformed registry entries degrade to "unmanaged dir" rather than errors; downgrade-safe because older builds never touch the file.
  • MCP recycle: stopServersWithKeyPrefix only stops matching workspaces' server sets; they restart lazily on next use, same as the idle-timeout path. No behavior change for non-plugin servers.
  • Everything is experiment-gated: with agent-plugins off, the service throws, the section/palette entry hide, and no new code paths run.

Judgement calls

  • install re-fetches the exact consented SHA (direct SHA fetch, falling back to branch clone + HEAD verification) rather than keeping the preview clone on disk between preview and confirm — a stateless preview means cancel/crash cannot leave partial state, at the cost of a second shallow clone on confirm.
  • The installed tree drops .git (plain content snapshot): the registry holds all provenance, updates replace the dir wholesale, and a live checkout would only invite in-place edits that updates discard.
  • Update refuses upstream renames (plugin.json#name changed): container-entry names are identity (instanceId → PLUGIN_DATA, workspace overrides), so renames require uninstall/reinstall.
  • Uninstall stops that plugin's running MCP servers before deleting the tree, mirroring the update-recycle rationale.
  • Update All Plugins applies only update-available entries; moved tags stay per-plugin manual (a mutated tag deserves the section's warning, not a bulk apply).

Deferred (per §5/§6 of the design)

  • v2: monorepo subpath installs (sparse checkout; grammar + schema already in place), content-addressed store + symlinked container entries, dev-mode/local-path installs, unmanaged-dir adoption ("convert to managed"), Pin row action, bun run debug plugin … CLI + /plugin slash command.
  • v3: repo-declared prompt-on-trust team plugins, archive+sha256 / seed dirs for air-gap, restore-from-lock, catalogs/marketplace (Claude marketplace import adapter only on demonstrated demand).
  • Explicit non-goals: background/auto-update (per-entry autoUpdate boolean reserved in the schema, unused), agent-facing install tool, Claude Code marketplace compatibility.

Post-review hardening (Codex rounds 1–20)

20 review rounds of fixes folded into this diff

Highlights beyond the original plan (full round-by-round history in the PR review threads):

  • Registry durability: standalone plugins.json with lossless raw-document writes (unknown envelope/entry/tombstone fields from newer builds survive rewrites), strict-mode reads for mutations vs lenient reads for list, raw-entry collision checks, non-ENOENT read errors refuse mutations.
  • Uninstall override pruning: pre-commit workspace enumeration, persisted pendingOverridePrunes tombstones (pessimistic commit, retry on list, reconciliation against deleted workspaces, reinstall gate) so a temporarily unreachable checkout can never let a reinstall silently re-enable a pruned server.
  • Workspace MCP overrides optimistic concurrency: workspace.mcp.get returns { overrides, revision }; set requires expectedRevision and rejects stale dialog snapshots (serialized check-and-set), and the uninstaller's prune retries on conflict — a stale open dialog can no longer resurrect pruned plugin: keys.
  • MCP recycle vs in-flight startups: monotonic prefix-invalidation epochs; closeInvalidatedInstancesThenPublish re-scans until the invalidation clock is stable and publishes synchronously in the same continuation, closing the microtask window where a mid-startup plugin swap could publish (and keep alive) a server from a deleted tree. Regression test interleaves the swap into the exact yield window.
  • Consent preview parity: symlinked skill dirs are disclosed (with containment warnings for escaping symlinks), matching runtime discovery.
  • Mobile: break-all on plugin name/location/source lines + pinned 390px story with a max-length-name overflow assertion.

Two P2 follow-ups are documented (not in this PR) in this comment: stale workspace-switch modal loads, and prefix-stop retry-marker publish ordering.

Rebase + experimental label

  • Rebased onto main (squashed to one feature commit): merged this branch's install contract with main's independently-landed agentPlugins.ts oRPC schemas (slash commands/composition inspector), re-ported the stable-clock publication onto the MCP SDK v2 mcpServerManager, and adopted main's SSH→HTTPS clone fallback into the extracted gitUrls.ts (installer records the primary URL only, documented).
  • The Plugins section is now marked experimental the same way as Backup: FlaskConical nav icon + in-section warning banner.
  • Re-dogfooded the full lifecycle post-rebase in a fresh dev-server-sandbox (install → consent preview incl. a deliberately-invalid mcp.json diagnostic → update badge → atomic update → read-only MCP row → uninstall with data-preservation default); screenshots in the workspace transcript.

Generated with mux • Model: anthropic:claude-fable-5 • Thinking: xhigh • Cost: $327.63

@mintlify

mintlify Bot commented Aug 8, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟢 Ready View Preview Aug 8, 2026, 6:37 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b9d7245ce

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/common/config/schemas/appConfigOnDisk.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all five Codex findings in b9c9a10:

  • Traversal names (P1) — the plugin-name grammar (§5 pattern, now shared via src/common/utils/agentPluginName.ts) is enforced in the registry entry schema and asserted in targetPathFor before any filesystem mutation; entries named ./../a/../b are dropped on read and can never resolve outside the container. Test: "registry survives config.json rewrites and drops traversal names on read".
  • Downgrade preservation (P1) — correct: .passthrough() only affects schema validation; older builds rebuild config.json from known fields on save. Followed §6-Q2's own contingency ("migration to a separate file"): the registry now lives in a standalone ~/.mux/plugins.json that older builds never rewrite. Test: registry survives editConfig config.json rewrites.
  • Registry write observability (P2) — solved by the same move: the service owns the file and its atomic write throws, so install rolls back the promoted dir ("Failed to persist the plugin registry"), uninstall writes the registry before deleting the tree, and a failed update write keeps the stale lockedSha (badge stays, retry self-heals). Test: "install rolls back the promoted dir when the registry write fails".
  • Fallback clone into non-empty dir (P1) — the staging dir is reset before the branch-clone fallback. Test: "falls back to a branch clone when the remote refuses direct SHA fetches" (file:// remote with uploadpack.allowAnySHA1InWant=false).
  • Keyboard rule (P1) — added palette commands: Install Agent Plugin… (opens Settings → Plugins with the add form expanded), Check for Plugin Updates (toast + navigates when updates exist), Update All Plugins (applies update-available; moved tags intentionally stay per-plugin manual since mutated tags deserve the section's warning). Uninstall/per-plugin update remain reachable via standard focus navigation within the section.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9c9a1062a

ℹ️ 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".

Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/browser/utils/commands/sources.ts Outdated
Comment thread src/browser/utils/commands/sources.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all four round-2 findings in edcdfa0:

  • Registry restore on removal failure — uninstall now stages the tree out of the container (rename into the staging root) before the registry write: a locked/undeletable tree fails the rename with the install fully intact, and a failed registry write renames the tree back. Deleting the staged tree is best-effort (stale-dir reclamation covers leftovers). Test: "uninstall restores the registry entry when the tree cannot be staged out" (read-only container forces the rename failure, then the retry succeeds).
  • Add panel with section already mounted — the intent module now supports subscription; the mounted section subscribes and expands the add panel immediately, while the useState initializer still covers the palette → fresh-mount path.
  • Mutation errors clobbered by refresh — update/uninstall re-assert the operation error after the refresh (whose success path clears error state); a failed uninstall also keeps the confirmation open instead of dismissing it.
  • Per-plugin check errors — both Check for Plugin Updates and Update All Plugins now distinguish status: "error" entries: unreachable remotes surface as "Update check failed for …" (with navigation to the section) instead of masquerading as "All plugins are up to date."

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edcdfa05fe

ℹ️ 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".

Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx
Comment thread src/browser/utils/commands/sources.ts
Comment thread src/node/services/agentPlugins/sourceInput.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed all three round-3 findings:

  • Keyboard path for uninstall — new Uninstall Agent Plugin… palette command using the palette's select prompt (async getOptions over agentPlugins.list(), managed entries only). Submission publishes a confirm-uninstall intent and opens the section, landing the user in the existing confirmation flow with the plugin-data checkbox — the palette never deletes directly.
  • Mounted-section staleness after bulk updates — the intent module is now a typed bus (open-add-panel / confirm-uninstall / refresh); Update All Plugins publishes refresh after its mutations, so a mounted section re-queries list + update checks instead of showing stale versions/badges. Unmounted sections still consume the buffered intent on mount.
  • Tilde expansion~/~/… sources resolve against os.homedir() before git sees them (git is spawned via execFile, no shell). Grammar test added.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6503d2fe4

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/browser/utils/commands/sources.ts Outdated
Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx
Comment thread src/node/services/agentPlugins/sourceInput.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc68e771d7

ℹ️ 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".

Comment thread src/node/services/mcpServerManager.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/browser/utils/commands/sources.ts
Comment thread src/node/services/agentPlugins/installService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a2bd105f4

ℹ️ 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".

Comment thread src/node/services/mcpServerManager.ts
Comment thread src/node/services/mcpServerManager.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0443e1af3e

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/browser/features/Settings/Sections/PluginsSettingsSection.tsx
Comment thread src/node/services/agentPlugins/installService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc141c4c7e

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6daf6aec0

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1fd47e8cd

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6965bc298

ℹ️ 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".

Comment thread src/browser/utils/commands/sources.ts Outdated
Comment thread src/browser/utils/commands/sources.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e576538423

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

…-install-ux

Conflict resolution:
- discovery.ts: keep discoverAgentPluginAt + discovery gate; adopt main's
  one-line discoverAgentPlugins doc (canonical shadowing)
- workspaceMcpOverridesService.ts: adopt main's canonical-first
  getOverridesFilePaths array API (loadOverrides scan, canonical write
  target, multi-path rm) while keeping this branch's exit-code-checked
  removal, mode threading, cross-process runExclusive lock, CAS validation,
  and prunePluginOverrideKeys (now pruning canonical AND legacy files)
- mcpConfig.ts: combine WorkspaceMCPOverrides type import with main's
  normalizeProjectMetadataIdentityPath import
- installService.ts: loadPluginMcpServers ctx renamed muxHome -> xumHome
- tests: main's new legacy/canonical tests adapted to {overrides, revision}
  return shape; CAS/publish tests read the canonical .xum file the save now
  writes
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 918bcbf0f7

ℹ️ 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".

Comment thread src/node/services/workspaceService.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 918bcbf0f7

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

Comment thread src/node/services/agentPlugins/installService.ts
…e gate, cross-process server invalidation)

1. crossProcessLock: replace read-then-unlink reclamation with an atomic
   rename-to-quarantine + content verification. A delayed reclaimer that
   raced a concurrent reclaim-and-acquire now restores the new owner's
   lock instead of deleting it (a clobbered third-party wx-create is
   caught by its post-create ownership re-read).

2. Update capability gate now covers the full install-consented surface:
   skill advertisements (name + description, which interpolate into the
   model-visible skill index on every request) reject additions AND
   rewording; agent/workflow/slash-command additions reject (consent
   listed a specific component set). Removals still apply freely.

3. Cross-process MCP server invalidation: MCPServerManager reads the
   installer's on-disk mutation epoch before every serve and retires
   cached plugin-prefixed instances when a sibling process's
   install/update/uninstall bumped it; update() bumps explicitly on the
   journal-less no-old-tree path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fac3179c6d

ℹ️ 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".

Comment thread src/node/utils/main/crossProcessLock.ts Outdated
Comment thread src/node/services/workspaceService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts
… init teardown ordering)

1. Staging clones/fetches/checkouts now run with GIT_NO_HOOKS_ENV: a user
   with a relative global core.hooksPath would otherwise execute an
   attacker-controlled repository's post-checkout hook during Preview,
   before any consent UI appears.

2. fork(): the sanitization-abort cleanup aborts background init and
   AWAITS its termination (runBackgroundInit now returns a settled
   promise) before deleting the fresh worktree, so the delete cannot race
   init's writes/open handles and orphan the worktree.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 54+55 fixes on 5c02797:

  • crossProcessLock: atomic rename-quarantine reclamation with content verification and new-owner restore (+ tests)
  • Update capability gate extended to the full install-consented surface: skill advertisement additions/rewording rejected; agent/workflow/slash-command additions rejected (+ tests)
  • Cross-process MCP server invalidation: managers read the installer's mutation epoch before every serve and retire cached plugin instances when a sibling process mutated a plugin (+ test)
  • Staging git operations run with GIT_NO_HOOKS_ENV (hooks disabled before consent)
  • fork() sanitization-abort awaits background-init termination before deleting the worktree

…ate detection, skill dir-name validation, canonical path in error)

1. crossProcessLock: stale reclamation no longer deletes or renames the
   lock file (a delayed rename could clobber a newly confirmed owner in a
   three-process race). Reclaimers now serialize through a short-lived
   mkdir mutex and take ownership by atomically REPLACING the lock content
   in place — the path is never absent during reclamation, so no third
   process can slip in a wx create, and no restore branch exists. Mutex
   ownership is re-verified immediately before the replacing rename.

2. parseRegistryEntries (strict): duplicate names are detected across RAW
   entries before schema filtering, so a valid entry colliding with a
   same-name newer-version row refuses the mutation instead of update()
   patching / uninstall() deleting both rows (upgrade-downgrade rule).

3. Preview collectSkills mirrors runtime discovery: invalid skill
   directory names are skipped with a warning, and frontmatter names are
   validated against the directory name — the consent preview no longer
   promises a skill that never loads, and the update capability surface
   cannot misclassify one as an addition.

4. Sanitization-failure message now names the canonical
   .xum/mcp.local.jsonc (and legacy .mux fallback) instead of only .mux.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 56 fixes on 84964a0:

  • crossProcessLock reclamation redesigned: mkdir-mutex-serialized, in-place atomic content replacement — the lock path is never absent during reclaim and no restore branch exists (+ tests incl. a never-absent poller)
  • Strict registry mutations detect duplicate RAW names before schema filtering (+ test)
  • Preview skill collection validates directory names like runtime discovery (+ test)
  • Sanitization-failure message names canonical .xum/mcp.local.jsonc

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84964a03be

ℹ️ 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".

Comment thread src/node/utils/main/crossProcessLock.ts
Comment thread src/node/utils/main/crossProcessLock.ts Outdated
Comment thread src/node/services/mcpServerManager.ts Outdated
Comment thread src/node/services/agentPlugins/installService.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 84964a03be

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

Comment thread src/node/services/workspaceService.ts
…ity, serialized sweep, full skill fingerprint)

1. crossProcessLock publication is now atomic-with-content: the holder
   record is written to a temp file and hard-linked into place (exclusive
   EEXIST), so no observer can ever read a partially written lock and
   misjudge it corrupt mid-publication. Defense-in-depth for non-atomic
   writers from other builds: corrupt content younger than a 2s grace is
   retried, not reclaimed.

2. Release serializes through the same mkdir mutex as reclamation, making
   its verify-then-unlink atomic against a reclaimer replacing the file —
   a holder releasing at the stale ceiling can no longer delete the
   successor's confirmed lock. Bounded retries; on persistent contention
   the file is left for stale reclamation (never mis-deleted).

3. MCPServerManager cross-process invalidation: check+sweep runs inside a
   serialization queue and the observed token publishes only AFTER the
   sweep completes, so a concurrent serve cannot observe the token as
   handled while instances are still being retired; failed sweeps leave
   the token unpublished for retry.

4. Update capability fingerprint covers every model-visible skill field:
   description, when_to_use/when-to-use, and advertise (a hidden-to-
   visible flip or new steering guidance is re-consent territory).
Task worktrees materialize through TaskService's orchestrateFork flows —
outside WorkspaceService.create/fork — and queued/reserved launches even
register their workspace entry before the checkout exists, so an
uninstall's override pruning enumerates a path with nothing to prune and
the later materialization restores a committed stale plugin: enable.

WorkspaceService.sanitizeMaterializedTaskWorkspace (public wrapper over
the registration-time sanitizer, host-local runtimes only) now runs in
both TaskService materialization sites after the checkout exists and
BEFORE init/send; failures fail the launch (reserved: markTaskLaunchFailed
via throw; create: rollbackFailedTaskCreate + Err). Shared-parent
(isolation none) checkouts skip — the parent's consent context is alive.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 57 fixes on cdec127:

  • Lock publication is atomic-with-content (temp + hard-link, exclusive EEXIST) so partial reads are impossible; fresh corrupt content gets a bounded grace before reclaim
  • Release serializes through the reclaim mutex — verify-then-unlink can no longer delete a successor's confirmed lock
  • Manager cross-process check+sweep serialized; observed token publishes only after the sweep completes
  • Skill fingerprint covers description + when_to_use + advertise (all model-visible fields)
  • Task worktrees (both TaskService materialization flows) sanitize stale plugin: overrides before init/send; failures fail the launch

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdec127f84

ℹ️ 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".

Comment thread src/node/utils/main/crossProcessLock.ts Outdated
Comment thread src/node/services/taskService.ts
Comment thread src/node/services/taskService.ts
Comment thread src/node/services/agentPlugins/sourceInput.ts
Comment thread src/node/services/agentPlugins/installService.ts
…tize ordering + cleanup, git transport whitelist, strict agent preview)

1. crossProcessLock release: last-instant mutex.owns() re-check before the
   rm, mirroring reclamation — a >15s stall between the token read and the
   unlink can no longer delete a successor published by a mutex-breaking
   reclaimer.

2. TaskService immediate create: sanitization moved BEFORE
   emitWorkspaceMetadata (pre-announcement invariant) so no subscriber can
   send to the running-status task while sanitization is still pending.

3. TaskService reserved launch: sanitization failure now reclaims the
   materialized worktree/session via cleanupMaterializedTaskWorkspace
   before throwing (markTaskLaunchFailed alone leaked the checkout).

4. Staging git invocations set GIT_ALLOW_PROTOCOL=file:git:http:https:ssh
   (remote helpers like ext:: execute arbitrary commands and are not
   restricted by hook disabling), and the source parser rejects
   transport::address syntax outright with a clear message (+ test).

5. Preview agent collection mirrors runtime discovery exactly: regular
   files only (no symlinks) with AgentIdSchema-valid basenames, so consent
   never promises an agent that cannot load and updates cannot be rejected
   over a nonexistent capability.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 58 fixes on d41f5f3:

  • Lock release re-checks mutex.owns() at the last instant before rm (mirrors reclamation)
  • Immediate task create sanitizes before emitWorkspaceMetadata (pre-announcement invariant)
  • Reserved-launch sanitization failure reclaims the materialized worktree before throwing
  • GIT_ALLOW_PROTOCOL whitelist on every staging git invocation + parser rejection of transport::address remote-helper syntax (+ tests)
  • Agent preview collection mirrors runtime discovery exactly (regular files + AgentIdSchema basenames)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d41f5f341c

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts Outdated
collectAgentFiles now parses each candidate with
parseAgentDefinitionMarkdown (size cap included), mirroring runtime
discovery's readAgentDescriptorFromFile: a validly named agents/foo.md
with malformed content never loads, so it must not enter the consent
preview or the update capability fingerprint. An update that repairs
such a file in place now reads as 'adds agent foo.md' and is gated for
re-consent instead of passing with identical filename sets.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 59 fix on 5b1c874a3: agent preview/fingerprint collection now parses candidate definitions with the runtime's parseAgentDefinitionMarkdown (size cap included) — only runtime-valid agents are promised or fingerprinted, and an in-place repair of a malformed definition gates as an addition (+ test).

@chatgpt-codex-connector

This comment has been minimized.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 764b481daf

ℹ️ 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".

Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/node/utils/main/crossProcessLock.ts
Comment thread src/node/services/agentPlugins/installService.ts
Comment thread src/node/services/mcpServerManager.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 764b481daf

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

Comment thread src/node/services/agentPlugins/installService.ts
…rsist, sibling cache invalidation, post-startup token recheck, agent fingerprint)

1. crossProcessLock: acquiredAt is now a renewable lease — held locks
   re-stamp it every staleMs/4 inside the reclaim mutex, so a LIVE
   transaction legitimately exceeding staleMs (long uninstall pruning
   contended workspaces) can no longer be reclaimed on age alone; only
   holders that stopped renewing (crashed/wedged/pid-reused) age out.

2. Uninstall persists the post-commit workspace DELTA into the durable
   pendingOverridePrunes tombstone BEFORE pruning: a crash between the
   re-enumeration and a delta workspace's prune now leaves a retryable
   record; a failed persist skips the shrink (pessimistic record kept).

3. Cross-process sweep also clears the manager's latestWorkspaceOverrides
   cache: a sibling's on-disk prune was otherwise permanently shadowed by
   the stale cached enable, letting a same-name reinstall start without
   consent. Disk is authoritative after a cross-process mutation.

4. getToolsForWorkspace re-reads the mutation token AFTER publication: a
   sibling mutation beginning after the preflight read (invisible to the
   in-process epoch and the discovery bracket) now retires the
   just-published stale instances and rebuilds once from the new tree.

5. Agent capability fingerprint covers the whole parsed frontmatter
   (key-sorted): description (task-tool model-visible), subagent.runnable,
   base/ui/policy. Changed definitions behind unchanged filenames gate as
   re-consent; body-only (system prompt) changes ride the tree swap.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 60 fixes on d9f1a37:

  • crossProcessLock leases renew every staleMs/4 inside the reclaim mutex — live transactions never expire on age alone (+ test)
  • Uninstall persists the post-commit workspace delta into the durable tombstone before pruning; failed persist keeps the pessimistic record
  • Cross-process sweep clears latestWorkspaceOverrides (disk authoritative after sibling mutations)
  • getToolsForWorkspace re-checks the mutation token post-publication and rebuilds once from the new tree (+ test)
  • Agent capability fingerprint covers the whole key-sorted frontmatter; body-only changes ride the swap (+ test)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9f1a374b1

ℹ️ 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".

Comment on lines +2760 to +2764
} catch (error) {
deltaEnumerated = false;
log.warn(
"Failed to persist post-commit workspace delta into the prune tombstone; keeping the pessimistic record",
{ error: getErrorMessage(error) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve delta IDs when tombstone persistence fails

When post-commit enumeration finds new workspaces but this union write fails, setting deltaEnumerated to false only prevents the later shrink; the already-durable tombstone still lacks those new IDs. If the process then crashes during pruning, or pruning one of those workspaces fails, retryPendingOverridePrunes() revisits only the original IDs and can eventually remove the tombstone, allowing a same-name reinstall to reactivate the unpruned override. Retain a durable retry record for the delta instead of continuing without one.

Useful? React with 👍 / 👎.

Comment on lines +1575 to +1576
await this.retireCrossProcessPluginInstances();
return this.ensureWorkspaceServers(options, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Repeat the post-publication token check after rebuilding

When two sibling plugin mutations overlap one slow serve, the first token change enters this branch, but the rebuilt call is returned directly and therefore bypasses getToolsForWorkspace()'s post-publication check. A second mutation that starts after the rebuild's preflight/discovery can consequently publish and return another stale plugin instance, which remains usable until a later serve. Fresh evidence beyond the prior single-mutation finding is this direct return path; loop the token check/rebuild until publication is bracketed by an unchanged token.

AGENTS.md reference: AGENTS.md:L150-L150

Useful? React with 👍 / 👎.

// after a cross-process mutation (every override write persists before
// publishing), so drop the cache and let callers' fresh disk snapshots
// through.
this.latestWorkspaceOverrides.clear();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate recorded override snapshots during sibling sweeps

When this sweep detects a sibling uninstall, clearing only latestWorkspaceOverrides does not make the current manager disk-authoritative: ensureWorkspaceServers() copies that cache into options and stores it in lastWorkspaceRequestOptions before awaiting the sweep. After pruning completes and the same plugin name is reinstalled, getPrompt() can refresh from that retained pre-prune enable and start the default-disabled replacement server. Fresh evidence beyond the earlier latest-cache finding is this separate recorded-options path and its pre-sweep publication ordering; refresh or invalidate those snapshots as part of the sweep.

AGENTS.md reference: AGENTS.md:L150-L150

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: d9f1a374b1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ 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.

Comment on lines +1575 to +1576
await this.retireCrossProcessPluginInstances();
return this.ensureWorkspaceServers(options, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Security: Reload overrides before rebuilding after sibling mutations

Requires Agent Plugins, two Xum processes sharing one home, and a same-name reinstall. A caller can read the old enabledServers before a sibling prunes it; this rebuild then passes the same stale options back into startup. The replacement MCP server is default-disabled, but the stale key enables and spawns it. Fresh evidence beyond the earlier cache finding is that ensureWorkspaceServers captures per-call overrides before the new sweep clears the cache, so the clear cannot revoke that snapshot. Reload disk-authoritative overrides before rebuilding.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant