fix(plugins): guard registerChannel in setup-entry path with try/catch#85774
fix(plugins): guard registerChannel in setup-entry path with try/catch#85774SebTardif wants to merge 1 commit into
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open: the loader fix is focused and appears correct, but the external PR still needs real gateway-style proof that a throwing setup-entry channel is isolated while a healthy sibling plugin still loads. Canonical path: Close this PR as superseded by #82579. So I’m closing this here and keeping the remaining discussion on #82579. Review detailsBest possible solution: Close this PR as superseded by #82579. Do we have a high-confidence way to reproduce the issue? Yes from source inspection: current main has an unguarded setup-entry Is this the best way to solve the issue? Yes for the code shape: catching this call with Security review: Security review cleared: The diff only changes plugin-loader error isolation and tests; it does not touch dependencies, CI, credentials, permissions, or package execution sources. What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against ee61f79b901d. |
6bf0aaf to
89caf6b
Compare
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
The setup-entry channel loading loop wraps every registration call in try/catch except api.registerChannel(mergedSetupPlugin). A throw from registerChannel (malformed plugin config, unexpected getter, validation error) aborts the entire loop and silently drops all subsequent plugins. Wrap the call in the same recordPluginError + continue pattern used by the nine surrounding guards so one broken setup-entry channel cannot take down sibling plugins. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
89caf6b to
80b1081
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
ClawSweeper applied the proposed close for this PR.
|
|
Linking these two PRs for context: This PR (#85774) adds a try/catch around #82579 ( Current main still has the unguarded call: with no try/catch, confirmed via Key difference: #82579 addresses trust policy (which plugins are allowed to register channels), while this PR addresses error resilience (what happens when a permitted plugin's Worth considering whether #82579 should include the try/catch guard from this PR as part of its rework. |
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Port the registerChannel guard from #85774 so a permitted but broken setup-entry channel reports a diagnostic without aborting sibling plugin loads. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca> Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Problem
Every other plugin registration call in the
loadOpenClawPluginssetup-entry loop (9 total) is wrapped intry/catchwithrecordPluginError+continue. Theapi.registerChannel(mergedSetupPlugin)call at line 2307 is not. If it throws, the exception escapes the for-loop and aborts loading of all remaining plugins.Fix
Wrap the unguarded
api.registerChannel(mergedSetupPlugin)call in the same try/catch +recordPluginError+continuepattern used by the 9 surrounding registration calls. A throwing channel registration is now recorded as a plugin diagnostic and does not abort loading of healthy sibling plugins.Production diff is +10 lines in
loader.ts, +131 lines of test coverage.Real behavior proof
Behavior addressed: An unguarded
registerChannelcall in the setup-entry plugin loading path could abort loading of all remaining plugins when any single channel registration throws.Real environment tested: macOS, Node 24, pnpm, Vitest 4.1.7, from a fresh
upstream/mainworktree at/tmp/fix-register-channel-guard.Exact steps or command run after this patch:
Evidence after fix:
Compiled production code verification
The try/catch guard is present in the compiled plugin loader bundle:
Before this fix, line 5744 was just
api.registerChannel(mergedSetupPlugin);with no try/catch. A throw here would escape the for-loop (line 5667) and abort loading of ALL remaining plugins.Live gateway startup proof
The patched gateway starts, loads all 10 bundled plugins through the plugin loader, and runs cleanly:
All 10 plugins loaded successfully. No
[plugins] ... failed to register setup channelwarnings appear, confirming the catch block is not triggered during normal operation (it is defensive error handling for channel registration failures only).Vitest loader test suite (supplemental)
The two new tests exercise the exact bug scenario:
config.listAccountIdsis loaded. The getter survives the shallow spread inresolveSetupChannelRegistrationbut throws insidenormalizeRegisteredChannelPlugin->registerChannel. Verifies the error is recorded as a diagnostic viaexpectDiagnosticContaining.continuein the catch block prevents the throw from aborting the loop).The 1 failed test (
supports legacy plugins subscribing to diagnostic events from the root sdk) also fails on unmodifiedupstream/mainand is not related to this change.Observed result after fix: The compiled production loader wraps
registerChannel(mergedSetupPlugin)in try/catch withrecordPluginError+continue, matching all 9 surrounding registration calls. The gateway starts cleanly with 10 plugins. The test suite confirms that a throwing channel registration is caught, logged, and does not prevent sibling plugins from loading. 134/135 tests pass (1 pre-existing upstream failure unrelated to this change).What was not tested: Triggering the catch handler during a live gateway startup with a real external setup-entry channel plugin. The setup-entry path requires an installed external plugin that provides its own channel registration, which is not available in a standard local dev setup. The test exercises the exact same code path using a synthetic setup-entry plugin with a throwing getter that triggers inside
registerChannel.