Skip to content

fix(secretrefs): resolve external channel contracts#76449

Merged
joshavant merged 2 commits into
mainfrom
fix/channel-secretref-external-contracts
May 3, 2026
Merged

fix(secretrefs): resolve external channel contracts#76449
joshavant merged 2 commits into
mainfrom
fix/channel-secretref-external-contracts

Conversation

@joshavant

Copy link
Copy Markdown
Contributor

Summary

  • Problem: after the 2026.5.2 plugin split, channel SecretRef collection still used the bundled-only channel contract loader.
  • Why it matters: externalized channel plugins such as Discord and BlueBubbles could leave SecretRefs unresolved at runtime, while related status/setup/tool paths could crash or silently use stale config.
  • What changed: added a generic channel secret-contract loader for external plugin root sidecars, wired it into runtime snapshots and secret target discovery, and hardened Telegram disabled-account enumeration, BlueBubbles webhook auth, and late-bound web_search config.
  • What did NOT change (scope boundary): no config migration, no changelog entry, no bundled channel-specific behavior moved into core beyond the generic contract loading seam.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: channel SecretRef materialization still depended on bundled channel public artifacts, while 2026.5.2 externalized multiple channel plugins that publish their secret contracts as plugin package sidecars.
  • Missing detection / guardrail: no regression covered channel-only configs without plugins.entries, external plugin sidecar secret contracts, or raw SecretRef values reaching late webhook/tool/status paths.
  • Contributing context (if known): the broader plugin externalization refactor moved install/runtime ownership without a matching generic channel secret-contract discovery path.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/secrets/channel-contract-api.external.test.ts, src/secrets/runtime-external-channel-origin-discovery.test.ts, extensions/telegram/src/accounts.test.ts, extensions/bluebubbles/src/monitor.webhook-auth.test.ts, src/agents/tools/web-tools.enabled-defaults.test.ts.
  • Scenario the test should lock in: external channel contract sidecars are loaded, channel-only configs discover plugin origins, disabled Telegram accounts do not resolve tokens, BlueBubbles unresolved webhook password refs fail closed, and late-bound web_search uses the active runtime config.
  • Why this is the smallest reliable guardrail: each test isolates one broken seam without needing live Discord, BlueBubbles, Telegram, or Brave credentials.
  • Existing test that already covers this (if any): extensions/discord/src/setup-account-state.test.ts already covered unresolved Discord setup-state SecretRefs and now keeps that behavior explicit.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

SecretRef-backed channel credentials in externalized plugins resolve through the active runtime snapshot again. BlueBubbles webhook auth rejects unresolved SecretRef passwords without crashing, disabled Telegram accounts are skipped before token resolution, and sub-agent late-bound web_search uses the active runtime config.

Diagram (if applicable)

Before:
channels.discord.token SecretRef -> bundled-only contract lookup -> no assignment -> unresolved runtime value

After:
channels.discord.token SecretRef -> external plugin sidecar contract -> runtime assignment -> resolved value

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: the loader now reads secret contract sidecars from loadable external channel plugin roots. It uses plugin metadata ownership checks, loadable-origin filtering where available, root boundary checks, and hardlink rejection for non-bundled plugin files.

Repro + Verification

Environment

  • OS: macOS local, Linux Testbox
  • Runtime/container: Node 22 / pnpm
  • Model/provider: N/A
  • Integration/channel (if any): Discord, BlueBubbles, Telegram, Brave web_search
  • Relevant config (redacted): SecretRef-backed channel credentials and web_search provider config

Steps

  1. Apply only the regression tests to the pre-fix merge-base.
  2. Run the targeted tests for web_search, Telegram, BlueBubbles, and generic channel contract/runtime discovery.
  3. Run the same targeted tests and pnpm check:changed after the fix.

Expected

  • Pre-fix tests fail for unresolved external channel contracts/raw SecretRefs.
  • Fixed branch passes targeted tests and changed gate.

Actual

  • Pre-fix red proof: web_search passed config: undefined; Telegram resolved a disabled account SecretRef; BlueBubbles called .trim() on a SecretRef object; generic channel contract tests failed because loadChannelSecretContractApi did not exist and runtime still used the bundled-only loader.
  • Fixed branch passed the targeted suite and pnpm check:changed in Testbox after rebasing onto current origin/main.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: targeted red/green tests for generic external channel contracts, runtime origin discovery, Telegram disabled accounts, BlueBubbles webhook auth, and late-bound web_search runtime config; broad pnpm check:changed in Testbox.
  • Edge cases checked: external plugin outside the loadable origin set is skipped; unresolved BlueBubbles webhook SecretRef fails closed; channel-only config without plugins.entries still discovers plugin origins.
  • What you did not verify: live Discord, Telegram, BlueBubbles, or Brave API calls.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: external channel sidecar loading could include the wrong plugin when multiple plugins claim a channel id.
    • Mitigation: prefer exact channel-id ownership ordering and filter by loadable plugin origins when runtime discovery supplies them.
  • Risk: raw unresolved SecretRefs could still reach owner-specific runtime paths.
    • Mitigation: added focused guards for the observed BlueBubbles and Telegram paths, plus a broad audit for direct raw SecretInput handling.

@joshavant
joshavant requested a review from a team as a code owner May 3, 2026 04:40
@openclaw-barnacle openclaw-barnacle Bot added channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: telegram Channel integration: telegram cli CLI command changes agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels May 3, 2026
@clawsweeper

clawsweeper Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge.

Summary
The PR adds external channel secret-contract sidecar loading, wires it into runtime and secret-target discovery, and hardens BlueBubbles, Telegram, Discord setup status, and late-bound web_search SecretRef paths.

Reproducibility: yes. Source inspection gives high-confidence current-main paths for the bundled-only channel contract lookup, BlueBubbles SecretRef .trim() crash, Telegram disabled-account resolution order, and late-bound web_search config loss, although I did not run tests in this read-only review.

Next step before merge
The protected maintainer label requires explicit maintainer handling; the only concrete blocker I found is a missing changelog entry.

Security
Cleared: The diff is security-sensitive because it changes SecretRef loading and external plugin sidecar execution, but I found no concrete security or supply-chain regression beyond the intended root-bound contract loading.

Review findings

  • [P3] Add the required changelog entry — extensions/bluebubbles/src/monitor.ts:197
Review details

Best possible solution:

Land this PR after maintainer review, a changelog entry, and changed-gate proof, then let it close the linked regressions and supersede the narrower overlapping PRs.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection gives high-confidence current-main paths for the bundled-only channel contract lookup, BlueBubbles SecretRef .trim() crash, Telegram disabled-account resolution order, and late-bound web_search config loss, although I did not run tests in this read-only review.

Is this the best way to solve the issue?

Yes, mostly. The generic external channel contract loader plus owner-local guards follows the OpenClaw boundary better than channel-specific core fixes; the missing changelog is the remaining mechanical merge blocker I found.

Full review comments:

  • [P3] Add the required changelog entry — extensions/bluebubbles/src/monitor.ts:197
    This PR changes user-facing behavior for SecretRef-backed channel credentials and sub-agent web_search, but the diff does not touch CHANGELOG.md and the body says no changelog entry was added. OpenClaw policy requires user-facing fix changes to be recorded before merge.
    Confidence: 0.93

Overall correctness: patch is correct
Overall confidence: 0.82

What I checked:

Likely related people:

  • Jason: Current blame for the consolidated secret runtime, channel contract loader, BlueBubbles monitor, Telegram accounts helper, and web_search helper points to commit 53bd718, which created these current-main files in this checkout. (role: introduced current behavior; confidence: medium; commits: 53bd718a1ac5; files: src/secrets/channel-contract-api.ts, src/secrets/runtime.ts, extensions/bluebubbles/src/monitor.ts)
  • joshavant: Local history shows Josh Avant on broad SecretRef support and related gateway/auth SecretRef hardening, making him a likely routing candidate for SecretRef contract behavior. (role: adjacent SecretRef maintainer; confidence: medium; commits: 806803b7efe2, 72cf9253fcb5, 0e4245063f20; files: src/secrets, src/config, extensions/discord)
  • Peter Steinberger: Recent history shows repeated channel runtime and public contract refactors, including public channel contract test seams and Discord/Telegram runtime boundary work. (role: channel/plugin contract maintainer; confidence: medium; commits: fc3246d8fd86, 7db10d21036a, 550c51bb6ef3; files: src/plugin-sdk, src/channels, extensions/discord)
  • Vincent Koc: Recent main history includes fix(secretrefs): preserve exec resolver env, which is adjacent to the runtime SecretRef materialization paths this PR changes. (role: recent SecretRef maintainer; confidence: medium; commits: b258c3fc6578; files: src/secrets)

Remaining risk / open question:

  • I did not execute tests because this was a read-only review; validation here is from source inspection, PR diff review, and the PR-provided Testbox claim.
  • The PR spans secret loading, CLI target discovery, channel plugins, and web_search routing, so maintainer review should require changed-lane proof before merge.

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

@joshavant
joshavant force-pushed the fix/channel-secretref-external-contracts branch from 630e5a4 to 7ec0dbe Compare May 3, 2026 04:48
@joshavant
joshavant merged commit b1f8172 into main May 3, 2026
10 checks passed
@joshavant
joshavant deleted the fix/channel-secretref-external-contracts branch May 3, 2026 04:48
vincentkoc pushed a commit to mogglemoss/openclaw that referenced this pull request May 4, 2026
Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.
joshavant added a commit that referenced this pull request May 4, 2026
…#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in #76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs #76371. Fixes #77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…openclaw#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…openclaw#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…openclaw#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…openclaw#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…openclaw#77421)

* fix(secretrefs): resolve external channel contracts in dist/ sidecars

Externalized channel plugins published to npm (e.g. @openclaw/discord
since 2026.5.2) keep their compiled secret-contract-api artifact under
<rootDir>/dist/, per the package.json `openclaw.runtimeExtensions`
convention. The runtime contract loader added in openclaw#76449 only searched
the rootDir, so npm-installed plugins silently dropped their channel
SecretRef contracts: the runtime snapshot left `channels.<id>.token`
as an unresolved SecretRef, the plugin's `isConfigured` check then
returned false, and the gateway recorded `error: not configured`
without firing the usual channel startup logs.

Look in `<rootDir>/dist/` as well as `<rootDir>/`, preferring dist
when running from a built openclaw artifact and rootDir when running
from source. The new `loads dist/ secret-contract-api sidecars …`
test in channel-contract-api.external.test.ts mirrors the real
npm-package layout and fails without this change.

Refs openclaw#76371. Fixes openclaw#77416.

* docs: credit changelog contributor

---------

Co-authored-by: Magpie <magpie@local>
Co-authored-by: joshavant <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: telegram Channel integration: telegram cli CLI command changes maintainer Maintainer-authored PR size: L

Projects

None yet

1 participant