Skip to content

fix(plugins): guard registerChannel in setup-entry path with try/catch#85774

Closed
SebTardif wants to merge 1 commit into
openclaw:mainfrom
SebTardif:fix/register-channel-try-catch
Closed

fix(plugins): guard registerChannel in setup-entry path with try/catch#85774
SebTardif wants to merge 1 commit into
openclaw:mainfrom
SebTardif:fix/register-channel-try-catch

Conversation

@SebTardif

@SebTardif SebTardif commented May 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Every other plugin registration call in the loadOpenClawPlugins setup-entry loop (9 total) is wrapped in try/catch with recordPluginError + continue. The api.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 + continue pattern 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 registerChannel call 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/main worktree at /tmp/fix-register-channel-guard.

Exact steps or command run after this patch:

Step 1. Build OpenClaw from patched source
cd /tmp/fix-register-channel-guard
pnpm install && pnpm build

Step 2. Verify the try/catch guard exists in compiled production code
grep -n -B2 -A8 "api.registerChannel(mergedSetupPlugin)" dist/loader-ButjA1ka.js

Step 3. Start the patched gateway (plugin loader runs cleanly)
OPENCLAW_HOME=/tmp/openclaw-proof-home timeout 15 node openclaw.mjs gateway

Step 4. Run loader test suite (135 tests)
node scripts/run-vitest.mjs src/plugins/loader.test.ts --reporter=verbose

Evidence after fix:

Compiled production code verification

The try/catch guard is present in the compiled plugin loader bundle:

$ grep -n -B2 -A8 "api.registerChannel(mergedSetupPlugin)" dist/loader-ButjA1ka.js
5742-continue;
5743-}
5744:try {
5745:api.registerChannel(mergedSetupPlugin);
5746:} catch (err) {
5747:recordPluginError({
5748:logger,
5749:registry,
5750:record,
5751:seenIds,
5752:pluginId,
5753:origin: candidate.origin,
5754:phase: "load",
5755:error: err,
5756:logPrefix: `[plugins] ${record.id} failed to register setup channel from ${record.source}: `,
5757:diagnosticMessagePrefix: "failed to register setup channel: "
5758:});
5759:continue;
5760:}

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:

$ OPENCLAW_HOME=/tmp/openclaw-proof-home timeout 15 node openclaw.mjs gateway
2026-05-23T10:46:15.865-07:00 [gateway] loading configuration...
2026-05-23T10:46:15.917-07:00 [gateway] starting...
2026-05-23T10:46:17.595-07:00 [health-monitor] started (interval: 300s, startup-grace: 60s, channel-connect-grace: 120s)
2026-05-23T10:46:20.603-07:00 [gateway] agent model: openai/gpt-5.5 (thinking=medium, fast=off)
2026-05-23T10:46:20.604-07:00 [gateway] http server listening (10 plugins: acpx, bonjour, browser, canvas, device-pair, file-transfer, meeting-notes, memory-core, phone-control, talk-voice; 4.7s)
2026-05-23T10:46:23.036-07:00 [gateway] ready
2026-05-23T10:46:30.538-07:00 [gateway] received SIGTERM; shutting down
2026-05-23T10:46:30.608-07:00 [shutdown] completed cleanly in 59ms

All 10 plugins loaded successfully. No [plugins] ... failed to register setup channel warnings 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)

$ node scripts/run-vitest.mjs src/plugins/loader.test.ts --reporter=verbose
 ✓ records a diagnostic when registerChannel throws in the setup-entry path 19ms
 ✓ keeps healthy sibling channel plugins loadable when a setup entry throws 14ms
 ✓ reports 'bundled-channel-setup-entry' loaded through the legacy plugin loader 7ms
 Test Files  1 passed (1)
 Tests  134 passed, 1 failed (135)

The two new tests exercise the exact bug scenario:

  • "records a diagnostic when registerChannel throws": A synthetic setup-entry plugin with a throwing getter on config.listAccountIds is loaded. The getter survives the shallow spread in resolveSetupChannelRegistration but throws inside normalizeRegisteredChannelPlugin -> registerChannel. Verifies the error is recorded as a diagnostic via expectDiagnosticContaining.
  • "keeps healthy sibling channel plugins loadable": Same throwing plugin is loaded alongside a healthy sibling. Verifies the healthy sibling still loads successfully (the continue in 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 unmodified upstream/main and is not related to this change.

Observed result after fix: The compiled production loader wraps registerChannel(mergedSetupPlugin) in try/catch with recordPluginError + 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.

@openclaw-barnacle openclaw-barnacle Bot added size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 23, 2026
@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

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 details

Best 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 registerChannel call, and channel normalization can throw while reading config helpers. I did not run tests in this read-only review, but the added fixture is a high-confidence reproduction shape.

Is this the best way to solve the issue?

Yes for the code shape: catching this call with recordPluginError and continue matches the neighboring setup-entry failure handling and preserves sibling plugin loading. The remaining gap is proof, not a different implementation direction.

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:

  • linked superseding PR: fix(plugins): block untrusted workspace setup-only channel loads #82579 (fix(plugins): block untrusted workspace setup-only channel loads) is still open as the canonical replacement.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • brokemac79: git blame on the setup-entry loader block attributes the current unguarded registerChannel path to f4b92f5. (role: introduced current loader behavior in available history; confidence: medium; commits: f4b92f5e6c70; files: src/plugins/loader.ts, src/plugins/loader.test.ts)
  • Peter Steinberger: Recent commits in the local main history touch plugin startup and plugin-related performance surfaces adjacent to this loader path. (role: recent plugin startup area contributor; confidence: medium; commits: edbd8333511c, f1226aeb6c76, 3e8fd4944fe1; files: src/plugins/sdk-alias.ts, src/plugins/channel-plugin-ids.test.ts, src/plugins/providers.ts)
  • hxy91819: Live GitHub search found related open maintainer work on setup-only channel loads at fix(plugins): block untrusted workspace setup-only channel loads #82579, which overlaps the loader/channel setup area even though it does not supersede this PR. (role: adjacent setup-only channel loader owner; confidence: low; files: src/plugins/loader.ts, src/plugins/loader.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against ee61f79b901d.

@openclaw-barnacle openclaw-barnacle Bot added size: XS proof: supplied External PR includes structured after-fix real behavior proof. and removed size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 23, 2026
@SebTardif SebTardif force-pushed the fix/register-channel-try-catch branch from 6bf0aaf to 89caf6b Compare May 23, 2026 17:54
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels May 23, 2026
@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

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>
@SebTardif SebTardif force-pushed the fix/register-channel-try-catch branch from 89caf6b to 80b1081 Compare May 23, 2026 19:05
@SebTardif

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this May 23, 2026
@SebTardif

Copy link
Copy Markdown
Contributor Author

Linking these two PRs for context:

This PR (#85774) adds a try/catch around registerChannel(mergedSetupPlugin) in the setup-entry loader path. If registerChannel throws (e.g. a listAccountIds getter error during channel normalization), the guard records a diagnostic and continues loading sibling plugins instead of crashing the loader.

#82579 (fix(plugins): block untrusted workspace setup-only channel loads) is a broader maintainer rework of the same loader area. It changes how registerChannel behaves for setup-only plugins by blocking untrusted workspace loads at the registration level.

Current main still has the unguarded call:

api.registerChannel(mergedSetupPlugin);

with no try/catch, confirmed via git show upstream/main:src/plugins/loader.ts.

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 registerChannel throws). These are complementary failure modes. If #82579 lands without the try/catch, a trusted setup-entry plugin that throws during registerChannel can still take down sibling plugin loading.

Worth considering whether #82579 should include the try/catch guard from this PR as part of its rework.

hxy91819 added a commit that referenced this pull request May 26, 2026
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>
hxy91819 added a commit that referenced this pull request May 27, 2026
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>
clawsweeper Bot pushed a commit that referenced this pull request May 27, 2026
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>
hxy91819 added a commit that referenced this pull request Jun 1, 2026
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>
clawsweeper Bot pushed a commit that referenced this pull request Jun 1, 2026
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>
clawsweeper Bot pushed a commit that referenced this pull request Jun 1, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant