fix(gateway): resolve plugin method scope from channel-pinned registry in gateway mode#92358
Conversation
In gateway mode the plugin registry is installed on the channel surface via pinActivePluginChannelRegistry(); the flat `activeRegistry` field is never set in that code path. resolveScopedMethod() and resolveSessionActionRegisteredScopes() read getPluginRegistryState()?.activeRegistry directly, so every plugin-registered gateway method / session action fails to resolve its scope, falls through to the ADMIN_SCOPE default, and rejects operator.write clients (e.g. a bridge plugin calling its own registered gateway method). Use getActivePluginChannelRegistryFromState() (channel.registry ?? activeRegistry) — the accessor already used throughout the channel/gateway code — so scope resolution works in both gateway and standalone/test modes. Add regression tests exercising the channel-pinned path; previously only the activeRegistry/standalone path was covered, which is why this shipped.
|
Thanks for the context here. I did a careful shell check against current Close: current main already solves the central plugin gateway method authorization problem with the merged attached-registry implementation, which is a better owner-boundary fix than landing this older resolver-level branch. The remaining channel-only session-action test in this PR is not tied to a proven current gateway production path. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. So I’m closing this as already implemented rather than keeping a duplicate issue open. Review detailsBest possible solution: Keep the current attached-registry authorization implementation from #94343; open a fresh focused issue if channel-only session-action scope resolution is observed in production. Do we have a high-confidence way to reproduce the issue? Yes at source level for the original active-registry-only plugin method scope failure, which was fixed on current main by the attached-registry authorization path. The branch's remaining channel-only session-action case is synthetic from source review, not a proven current production repro. Is this the best way to solve the issue? No for this branch as the landing path: current main's attached-registry authorization is the narrower maintainable fix because dispatch and scope metadata stay paired. A separate session-action issue should be proven before changing the global resolver. Security review: Security review cleared: No supply-chain issue was found; the diff is auth-sensitive, but the remaining concern is merge risk rather than a concrete security defect in the obsolete branch. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against fb022a2b07cb; fix evidence: commit 27f702d68fab, main fix timestamp 2026-06-19T11:56:24+01:00. |
|
ClawSweeper applied the proposed close for this PR.
|
fix(gateway): resolve plugin method scope from channel-pinned registry
🤖 AI-assisted (Claude Code). Author understands the change; human-run evidence below.
Problem
In gateway mode the active plugin registry is installed on the channel surface
via
pinActivePluginChannelRegistry(); the flatactiveRegistryfield is neverset in that code path. But
src/gateway/method-scopes.tsreadsgetPluginRegistryState()?.activeRegistrydirectly in two places:resolveScopedMethod()→gatewayMethodDescriptorslookupresolveSessionActionRegisteredScopes()→sessionActionslookupSo in gateway mode every plugin-registered gateway method / session action
fails to resolve its declared scope, falls through to the
ADMIN_SCOPEdefault, and is then rejected for any
operator.writeclient — e.g. a bridgeplugin calling its own gateway method registered with
operator.write.method-scopes.tsis the only place in the channel/gateway code still readingactiveRegistrydirectly; the rest of the codebase already usesgetActivePluginChannelRegistryFromState()(which returnschannel.registry ?? activeRegistry). The existing tests only exercised thesetActivePluginRegistry/activeRegistrypath, never the channel-pinned(gateway) path — which is why this shipped.
Fix
Use
getActivePluginChannelRegistryFromState()for both lookups, so scoperesolution works in both gateway and standalone/test modes. One-line import
swap + the two call sites.
Tests
Adds regression tests to
method-scopes.test.tsexercising the channel-pinnedpath (
pinActivePluginChannelRegistry) for both a gateway method and a sessionaction — the path that previously had no coverage.
Real behavior proof
Built
main(Node 24.16,pnpm build) and ran the new tests against the realresolver + the real
pinActivePluginChannelRegistry(not a mock):Without the fix (revert
method-scopes.ts, keep the test) — both fail:(i.e. the plugin method resolves to "unknown" → admin-only, and the session
action falls back to the broad CLI default — exactly the production symptom.)
With the fix — full suite green:
Production: this exact fix is deployed on our own OpenClaw gateway (source
install, v2026.5.28) running an MCP bridge plugin that registers
operator.writegateway methods; with the patch those calls succeed, without itthey were rejected as admin-only.
Gates
Against
main(Node 24.16):pnpm build✅,pnpm check(whole-repolint+typecheck) ✅, full
pnpm test✅ (incl.method-scopes.test.ts77/77).Notes
CHANGELOG.mdedit (per CONTRIBUTING).