Skip to content

🤖 fix: load managed plugin skills and MCP servers only after their review - #6587

Merged
ThomasK33 merged 8 commits into
fix/plugin-component-review-uifrom
fix/plugin-component-gate
Oct 14, 2026
Merged

ThomasK33 merged 8 commits into
fix/plugin-component-review-uifrom
fix/plugin-component-gate

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Upgrade effect (read first)

After this PR, every managed plugin's skills and MCP servers stop loading once, right after the upgrade, until the user reviews them. "Managed" means a plugin installed from Settings → Plugins (a row in plugins.json). Unmanaged plugins (~/.agents/plugins, repo plugins) are not affected.

  1. Settings → Plugins shows a "Needs review" badge on each affected plugin and one notice that says the components do not load until reviewed, with a Review button (🤖 feat: show plugins that need a component review in Settings #6570). The command palette has the same review command.

  2. One review per plugin restores loading. The review uses the existing update review panel and lists each skill and MCP server.

  3. Agents in a workspace see fewer skills and MCP tools until then. Nothing is deleted: imports, enablement and plugin data stay as they are.

  4. Downgrade: older builds ignore the consent records and load by name, as before. Upgrading again re-applies the gate.

  5. Release text: the settings notice says why the components stop loading and that each plugin needs one review after the upgrade. The Agent Skills docs have a "Component review" section, and the MCP servers docs link to it.

Why no backfill

#6498 is about a newer Xum parsing more of an installed package than the user saw at install time: an extra Codex skills root, or a stdio server that an older parser skipped. Those components load under a name the user approved. A first-run backfill would fingerprint whatever the current parser sees, which is exactly the content the user never reviewed. It would turn the bug into a stored approval. So an install without consent records counts as "nothing reviewed", and the user reviews once.

Summary

A managed plugin's skill or MCP server now loads only when a stored consent record matches its current fingerprint. Closes #6498 (part 4 of the stack, on top of #6570).

Implementation

  1. Skills: one predicate, isPluginSkillConsented (componentConsent.ts), fingerprints the skill's directory (relative to the plugin root) plus its parsed frontmatter, as the install review does. The skill index, agent_skill_read, agent_skill_list and skill:// reads all call it. A refused skill is skipped, so a same-name skill from another source can still serve.
  2. MCP servers: loadPluginMcpServers stores consentFingerprint on the plugin provenance. isPluginMcpServerAllowed (registry.ts) requires a matching record, in addition to the import and enablement checks. The provider drops an unreviewed server from the list, and the manager checks the same rule again at launch.
  3. Records come from 🤖 feat: record the reviewed fingerprint of each imported plugin component #6534 (install), 🤖 feat: review unconsented plugin components; updates record reviewed changes only #6557 (review and update) and 🤖 feat: show plugins that need a component review in Settings #6570 (UI).

Validation

New tests cover refusal and acceptance in the registry, the real MCP provider (including an args change under a reviewed name), the skill index, agent_skill_list and an end-to-end install. The end-to-end test installs a plugin, sees its skill and server load, strips the consent records, sees both stop loading, runs confirmComponentReview, and sees both load again. Each new test fails with the gate turned off. Existing fixtures now record real consent through the production fingerprint code.

Risks

  1. The upgrade effect above is intended. It is user-visible and needs one review per managed plugin.

  2. A fingerprint mismatch between the install review and the load path would block a component the user approved. The fixtures compute consent through the same production functions as the load path, and the end-to-end test covers the full cycle.

  3. A body-only update of a skill (its SKILL.md instructions) still applies without a review, as on main. The consent fingerprint covers the skill's folder and frontmatter, which closes the 🤖 fix: bind saved plugin imports and enablement to the reviewed component fingerprint #6498 case. Body review is a separate product proposal: 🤖 feat: review changed SKILL.md bodies in plugin updates #6617.

Size: about 400 production lines changed, the rest is tests and fixtures.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $42.85

@ThomasK33
ThomasK33 added this pull request to stack #6558 October 14, 2026 12:37
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T14:14:27.396891Z 5faba1f Manual request
🔒 Security Review ✅ Completed 2026-10-14T14:17:47.345117Z 5faba1f Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 57dda8c535

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 57dda8c535

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.

@ThomasK33
ThomasK33 force-pushed the fix/plugin-component-gate branch 2 times, most recently from 86efdd7 to 2a92074 Compare October 14, 2026 12:57
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

The branch is restacked on the latest #6557 and #6570 heads (2a92074). Please review this head.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 2a92074882

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 2a92074882

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.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Restacked on #6570 (685cbe5). No gate change.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 685cbe5e63

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 685cbe5e63

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.

@ThomasK33
ThomasK33 force-pushed the fix/plugin-component-gate branch 2 times, most recently from 15a711b to a2d05f6 Compare October 14, 2026 13:50
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 3 (head a2d05f6): restacked on the lower PRs. The settings notice and the docs now explain why managed plugin components stop loading after the upgrade and how to review them.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: a2d05f6c82

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: a2d05f6c82

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.

@ThomasK33
ThomasK33 force-pushed the fix/plugin-component-gate branch from a2d05f6 to 3b84a2c Compare October 14, 2026 13:56
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Restacked on #6570 (head 3b84a2c). No gate change.

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 3b84a2ca2d

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.

@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: 3b84a2ca2d

ℹ️ 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 docs/agents/agent-skills.mdx Outdated
Comment thread src/node/services/agentPlugins/mcpConfig.ts
Managed registry rows in tests now carry the consent an install review
would store, computed with the production plugin discovery, SKILL.md parse
and MCP loader (reviewedComponentConsent). Without it the #6498 load gate
correctly refuses their skills and MCP servers.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Extract named helpers instead of growing the gated functions:
pluginSkillConsentGate and noComponentConsent (componentConsent.ts),
ownerMcpPolicy/isReviewedServer/registryRowName/mcpPolicySnapshot
(registry.ts), managedPluginFields (discovery.ts), the SKILL.md reads and
readCandidateSkill (agentSkillsService.ts), and readContainedSkillFile
(agent_skill_list.ts). Behavior is unchanged.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
A managed skill or MCP server with no record, or a record for another
fingerprint, does not load; a matching record loads it; a frontmatter or
parser-visible server change under the same name is refused; unmanaged
plugins are not gated. One install-service test strips the records of a
real install and restores them through confirmComponentReview.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Now that the load gate exists, the Settings notice states the effect: the
skills and MCP servers of a plugin marked "needs review" do not load until
the user reviews them.

Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Round 5 (head 5faba1f, blockers only): both round 4 findings are fixed (docs wording here, the secret presence marker in #6534).

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 5faba1f06e

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 5faba1f06e

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.

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 48610b8 Oct 14, 2026
66 of 85 checks passed
@ThomasK33
ThomasK33 deleted the fix/plugin-component-gate branch October 14, 2026 15:28
yermakoffivan pushed a commit to yermakoffivan/mux that referenced this pull request Oct 14, 2026
…stom_header without headerName (coder#6487)

## Summary

Xum now loads three Codex `.mcp.json` shapes that it used to skip or
misreport. A stdio server with `env_vars` loads and receives the named
host variables. A remote entry with a `url` and no `type` loads as
streamable HTTP. An `api_key` auth block with `headerScheme:
custom_header` and no `headerName` is reported as malformed.

Refs coder#6297. This PR covers three of its items: "Codex `env_vars` on
stdio servers", "URL entries without `type`" and "`custom_header`
without `headerName`".

## Background

Codex review found these gaps after track A merged main (review threads
`PRRT_kwDOPxxmWM6rv0UV`, `PRRT_kwDOPxxmWM6rv0Ua` and
`PRRT_kwDOPxxmWM6rv0Uh`).

1. Codex defines `env_vars` as the way to pass selected host environment
variables to a stdio server ([OpenAI plugins
guide](https://developers.openai.com/api/docs/guides/agents-api/tools/plugins)).
Xum rejected the field as unknown, so the server did not load.
2. The Codex plugin parser reads a `url` entry without `type` as
streamable HTTP
([`codex-mcp/src/plugin_config.rs`](https://github.com/openai/codex/blob/main/codex-rs/codex-mcp/src/plugin_config.rs)).
Xum skipped it as an unknown type.
3. OpenAI's auth reference requires `headerName` for `custom_header`
([fixed bearer tokens and API
keys](https://developers.openai.com/plugins/build/plugins#fixed-bearer-tokens-and-api-keys)).
Xum listed the block as an unsupported setting instead of a malformed
one.

## Implementation

All three changes apply to the Codex dialect only. A root `mcp.json`
stays strict: `env_vars` and an untyped `url` are still errors there. A
new test covers that.

**`env_vars`.** The Codex translator turns `env_vars` into a new
`forwardEnv` list of names on the stdio server record. Xum copies the
semantics from Codex:

- `env_vars: null` and `source: null` count as absent, as Codex's
optional fields do.
- An item is a bare name or `{ name, source }` (`McpServerEnvVar` in
[`config/src/mcp_types.rs`](https://github.com/openai/codex/blob/main/codex-rs/config/src/mcp_types.rs)).
- Xum refuses `source: "remote"`. Codex refuses it for a plugin that
runs in the local environment (`bind_environment_env_vars` in
`plugin_config.rs`).
- Xum refuses `env_vars` on a remote server. Codex does the same
(`throw_if_set` in `mcp_types.rs`).
- At launch, `prepareStdioLaunch` copies only the named variables from
the host. It reads only own properties of the host environment, so a
name such as `constructor` is not an inherited value. It skips a name
that the host does not set, as Codex's `create_env_for_mcp_server` does
([`rmcp-client/src/utils.rs`](https://github.com/openai/codex/blob/main/codex-rs/rmcp-client/src/utils.rs)).
A configured `env` value wins over a host value.
- Xum refuses `PLUGIN_ROOT` and `PLUGIN_DATA` in `env_vars`, as it
already does in `env`.

Xum reads only the names at load time. It never stores or shows a host
value.

The install consent preview shows the names, for example `node (host
env: 'API_TOKEN')`. The consent fingerprint includes the names in the
order they first appear (duplicates are dropped), so an update that
adds, removes or reorders a name needs new consent. The server signature
includes them too, so a changed list restarts the server.

**Untyped `url`.** The translator infers `streamable-http` when an entry
has a `url` and no `type`. This mirrors how it already infers `stdio`
from a `command`.

**`custom_header` without `headerName`.** `readAuth` now adds a warning
that `headerName` is required, and it drops `headerScheme`. A bad
`headerName` already used this warning class.

## Validation

- I wrote each test first and saw it fail on `origin/main`. Then I made
it pass.
- `bun test` (pinned Bun 1.4.3) of `mcpConfig.test.ts`,
`openaiMcpAuth.test.ts`, `installService.test.ts` and
`mcpServerManager.test.ts`: 558 pass, 0 fail.
- `make static-check` passes.

## Resolved since the hold

- Consent for newly recognized servers is fixed on main by coder#6498 (stack
coder#6534, coder#6557, coder#6570, coder#6587). A managed plugin's server loads only when
its consent fingerprint matches what the user reviewed. This branch
merges that main: `forwardEnv` is an included field in the fingerprint
table (`componentConsent.ts`), so adding an `env_vars` name changes the
fingerprint and needs a review. The old update-only `stdioFingerprint`
helper is gone, because main's update review uses the same consent
fingerprint.
- The launch environment is fixed on main by coder#6581 (coder#6509). A plugin
stdio server starts from a minimal host environment. `env_vars` names
are copied into the server's declared `env`, so they reach the server on
top of that base. A new test launches a plugin server through the real
host runtime and checks that a named variable arrives and an unnamed one
does not.
- Docs: docs/config/mcp-servers.mdx ("Plugin server environment") now
describes `env_vars`.

## Risks

The risk is low. The `env_vars` and untyped-`url` changes apply only to
Codex `.mcp.json` files, and those entries failed to load before. A
stdio server without `env_vars` keeps the same launch environment,
fingerprint and signature, because each of them leaves out an unset
`forwardEnv`. Adding `forwardEnv` to the consent fingerprint does not
change the fingerprint of an existing server, because `pick` skips an
unset field.

---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking:
`high` • Cost: `$0.00`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high
costs=0.00 -->
yermakoffivan pushed a commit to yermakoffivan/mux that referenced this pull request Oct 15, 2026
…ed instances (coder#6765)

## Summary

On the Coder AI Gateway route, Xum now sends Anthropic's `drop_block`
thinking-binding control to anthropic-typed instances. Anthropic then
drops a replayed thinking block whose prefix changed and answers 200,
instead of rejecting the whole request with a signature 400 that Xum
must repair and retry. Before this PR, only the direct Anthropic route
sent it.

## Background

Opus, Sonnet and Haiku 5.5 and Fable 5.1 bind replayed thinking to the
system prompt and tool list before it. When either changes, a replayed
block fails its signature check. On one host the 400s ran at about 0.128
per Anthropic turn, and they clustered in the 20 minutes after each app
start (31, 20, 28, 17 and 14 per start on Oct 14).

Comparison of each workspace's last request before an app start with its
first request after it (8 starts, 281 workspaces, from `turn-envelope`
rows in `durable-events.jsonl`): the system prompt or tool list changed
in 95 of 281 (34%), against 195 of 2,590 (7.5%) between two turns in one
process.

| Cause | Workspaces | Status |
|---|---|---|
| Hot and pinned memory sections rebuilt from changed files | 52 | Fixed
by coder#6519 |
| Notion MCP server changed its tool schemas while the app was down | 18
| External |
| AGENTS.md or other context files edited | 15 | Real content change |
| `mcp_prompt_get` missing or added on the first turn | 10 | Open
(coder#6406) |
| Plugin skills and an MCP server hidden until the coder#6587 review | 5 |
One-time upgrade effect |
| An MCP server failed or was missing | about 10 | Open |

This PR does not stop these prefix changes. It removes the rejected
request and the retry that each of them cost.

## Implementation

- `anthropicBlockBindingRouteAvailable` keeps the direct-route check
unchanged. On the `coder` route it allows `drop_block` when the model
reaches an instance whose type is exactly `anthropic`, and the
request-level beta opt-out still turns it off. Bedrock-typed and other
instances stay excluded.
- The instance-type check moved into `anthropicTypedCoderRoute`, which
`between_tools` eligibility already used, so both decisions share it.
- The AI SDK sends the `thinking-binding-controls-2026-08-01` beta
header together with `thinking.blockBinding`.

## Validation

- `providerOptions.test.ts`: `coder:anthropic/…` ids and `anthropic:`
ids routed onto Coder through `canonicalRoutes` get `drop_block`.
Bedrock-typed instances and the beta opt-out do not. The test fails with
the source change reverted.
- **Live probe** through dogfood `claude-aws-us-east-2` (raw Messages
API, about $0.10), for Opus 5.5, Sonnet 5.5, Haiku 5.5 and Fable 5.1:
- A changed system prompt or tool list without `drop_block` returns the
same 400 as in the app log.
- The same request with `drop_block` and the beta header returns 200 and
reports `input_transformations: [{type: thinking_dropped, reason:
prefix_binding_mismatch}]`.
- An unchanged history with `drop_block` returns 200 and drops nothing.
- Adding `block_binding` to an unchanged conversation read the whole
cache (Haiku: 21,710 of 21,710 tokens), so turning it on costs no cache
miss.

## Risks

Low to medium. The change affects every Claude 5.5 and Fable 5.1 request
with thinking on the `coder` route to an anthropic-typed instance.
- Without the beta header, Anthropic rejects `block_binding` with "Extra
inputs are not permitted". The gateway passes the header through for
Anthropic upstreams, and its Bedrock path converts or strips the header
and the field together (coder/coder#29792). Bedrock-typed instances are
excluded anyway. An older gateway was not tested live.
- A dropped block still costs the cache miss of the changed prefix. Only
the 400 and the retry go away.

---

_Generated with `xum` • Model: `anthropic:claude-opus-5-5` • Thinking:
`high` • Cost: `$437.19`_

<!-- mux-attribution: model=anthropic:claude-opus-5-5 thinking=high
costs=437.19 -->
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.

🤖 fix: bind saved plugin imports and enablement to the reviewed component fingerprint

1 participant