fix(plugins): stop tool-discovery loads from clearing active providers#93276
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Prevents tool-discovery plugin-registry loads (tool-only scope with activate:false) from overwriting the active runtime registry (and thereby dropping provider-only plugins), and adds regression tests for the behavior.
Changes:
- Skip promoting tool-discovery registries to the active registry during standalone installs.
- Add Vitest coverage to ensure tool-discovery loads pin channel registries without clobbering the active registry.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/plugins/runtime/standalone-runtime-registry-loader.ts | Avoids replacing the active registry when toolDiscovery: true to prevent provider loss. |
| src/plugins/runtime/standalone-runtime-registry-loader.test.ts | Adds tests asserting active registry preservation and correct channel pinning for tool-discovery loads. |
|
Codex review: needs maintainer review before merge. Reviewed June 19, 2026, 6:52 AM ET / 10:52 UTC. Summary PR surface: Source +26, Tests +167. Total +193 across 8 files. Reproducibility: yes. The current-main source path sets activate:false/toolDiscovery:true and then installs that registry globally, and the PR body includes before/after terminal output showing provider count changing from 0 before the patch to preserved after the patch; I did not execute the repro in this read-only review. Review metrics: 2 noteworthy metrics.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the request-local tool-discovery fix after plugin/runtime signoff, a PR-body refresh that matches the final behavior, and exact-head validation; keep the adjacent cached descriptor retention work in #82562 separate. Do we have a high-confidence way to reproduce the issue? Yes. The current-main source path sets activate:false/toolDiscovery:true and then installs that registry globally, and the PR body includes before/after terminal output showing provider count changing from 0 before the patch to preserved after the patch; I did not execute the repro in this read-only review. Is this the best way to solve the issue? Yes. The final request-local approach fixes the clobber at the standalone loader boundary and carries the prepared registry to catalog/MCP resolution without adding another global fallback path; the remaining question is owner acceptance of the compatibility-sensitive registry semantics. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 92d1f04de340. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +26, Tests +167. Total +193 across 8 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
@clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. |
e07d482 to
987b18f
Compare
|
@clawsweeper re-review |
2b6218d to
97b596a
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
Tool-discovery plugin loads run with activate:false and a tool-only plugin scope, so the registry they build omits provider-only plugins. installStandaloneRegistry promoted that registry to the active registry, so a tool-discovery load (e.g. the gateway tools-catalog channel load behind agents.list) replaced the live active registry and wiped its providers. Promote a tool-discovery load to the active registry only when none exists yet (cold start); once an active registry is present it is only pinned to its requested channel/http-route surface, preserving the live providers. Non-tool-discovery loads (e.g. the migration provider runtime, which reads off the active registry) are unchanged. Source plugin tool catalog metadata and declared tools from the raw pinned channel registry in addition to the active registry, so tool-only plugins keep their display name, description, risk, and tags even when the channel selector returns an active registry that has channels. The active registry stays authoritative for duplicate keys. Adds loader and tools.catalog regression coverage, including the active-with-channels plus pinned-tool-only case.
97b596a to
f2c4978
Compare
|
Land-ready verification:
Known gap: broad local |
Keep plugin tool discovery request-local, preserve active provider/channel registries, and carry the prepared registry through MCP and catalog resolution. Co-authored-by: 郑苏波 (Super Zheng) <[email protected]>
Summary
activate: falseand a tool-only plugin scope, so the registry they build omits provider-only plugins.installStandaloneRegistryunconditionally calledsetActivePluginRegistry, so those loads replaced the live active registry and wiped itsproviders. The gatewaytools-catalogchannel load behindagents.list(and theresolvePluginToolscold-load) hit this path, so a bundled provider plugin disappeared at runtime after the first tool-discovery load.channel/http-routesurface, so the live providers are preserved. The cold-start promotion still initializes runtime state (cache key /workspaceDir), so later surface lookups hit and the bootstrap path does not reload redundantly.activate !== false) and the migration provider runtime, which intentionally relies onsetActivePluginRegistryand does not settoolDiscovery.installStandaloneRegistry(cold-start promotion vs pin-only) and thetools.catalogmetadata merge (active + selected channel + raw pinned channel).Linked context
No linked issue; this is a standalone runtime bug fix. Not requested by a maintainer.
Real behavior proof (required for external PRs)
agents.list/tools-catalogcall),getActivePluginRegistry().providersdropped to an empty array, so a bundled provider-only plugin vanished from the live runtime even though it was loaded at startup.setActivePluginRegistry(activeprovidersgoing from 1 to 0 on the tool-discovery stack). Verified after the patch with a Node reproduction (tsx) driving the real loader/runtime source on this branch (Node 22, this checkout).npx tsx __repro_provider_clobber.mtsit seeds an active registry that carries a provider-only plugin, then drives the realensureStandaloneRuntimePluginRegistryLoadedchannel tool-discovery path that the gatewaytools-catalog/agents.listrelies on, and prints the activeproviderscount before and after.providersstays at 1 and the active registry object is not replaced, so the live provider list survives.agents.listround-trip inside the packaged gateway binary was not re-run after a full rebuild; provider preservation was confirmed through the Node reproduction and the loader regression coverage instead.tsxrather than the fully repackaged gateway binary, because rebuilding and re-syncing the packaged runtime into the host application is out of band for this checkout. The pre-fix symptom itself was captured in the live gateway runtime via console instrumentation.Reproduction script used above
Tests and validation
node scripts/run-vitest.mjs src/plugins/runtime/standalone-runtime-registry-loader.test.ts src/gateway/server-methods/tools-catalog.test.ts src/plugins/tools.optional.test.ts-> gateway shard 16/16, plugins shard 73/73.pnpm tsgo:coreandpnpm tsgo:test:srcare clean for the changed files;git diff --checkis clean.standalone-runtime-registry-loader.test.ts): atoolDiscoverychannel load pins the channel surface without replacing an existing active registry; atoolDiscoveryactive load does not replace an existing active registry; atoolDiscoveryload initializes the active registry when none exists (cold start); a non-toolDiscoveryactive load still installs as the active registry (migration provider path).tools-catalog.test.ts): plugin tool metadata is projected from the pinned channel registry; and from the raw pinned channel registry even when the channel selector returns an active registry that has channels (the active-with-channels + pinned-tool-only case).providersdropping from 1 to 0 (active registry replaced) before the guard was added.Risk checklist
Yesonce an active registry exists, provider-only plugins are no longer wiped from it by tool-discovery loads.Nothe migration provider runtime load has notoolDiscoveryflag and still installs as the active registry exactly as before.No.tools.catalogmetadata source.activate !== false) and the migration-provider path are unchanged; on a true cold start the promoted tool-only registry is replaced by the first full activation; catalog metadata reads the raw pinned channel registry so tool-only plugins are not hidden by the channel selector; the loader andtools.catalogbranches are covered by regression tests.Current review state
setCachedPluginRegistry/getReusableCachedPluginRegistry) is keyed off the load cache key and is independent ofsetActivePluginRegistry, so skipping the active-registry write does not cause extra plugin reloads.tools.catalognow merges metadata and declared tools from the raw pinned channel registry (getPinnedActivePluginChannelRegistry) in addition to the active and selected channel registries, so a pinned tool-only registry is read even whengetActivePluginChannelRegistry()selects an active registry that has channels. Added a regression for the active-with-channels + pinned-tool-only case.