Skip to content

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

Merged
joshavant merged 2 commits into
openclaw:mainfrom
mogglemoss:fix-channel-contract-api-dist-lookup
May 4, 2026
Merged

fix(secretrefs): resolve external channel contracts in dist/ sidecars#77421
joshavant merged 2 commits into
openclaw:mainfrom
mogglemoss:fix-channel-contract-api-dist-lookup

Conversation

@mogglemoss

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Externalized channel plugins published to npm (e.g. @openclaw/discord since 2026.5.2) keep their compiled secret-contract-api artifact under <rootDir>/dist/, but the runtime contract loader added in fix(secretrefs): resolve external channel contracts #76449 only searches <rootDir>/. So the loader returns undefined for the real-world npm-package layout, the runtime snapshot leaves channels.<id>.token as an unresolved SecretRef, and channel startup silently bails with error: not configured.
  • Why it matters: [Bug] @openclaw/discord 2026.5.2 channel startup crashes on SecretRef-backed token (env:default:DISCORD_BOT_TOKEN unresolved) #76371 was closed by fix(secretrefs): resolve external channel contracts #76449 (broad "fix(secretrefs): resolve external channel contracts"), but on a clean 2026.5.3-1 install the bug is still present for any user with channels.discord.token = { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" } configured (the docs-canonical SecretRef shape). The failure mode just shifted from a loud unresolved SecretRef crash on 2026.5.2 to a silent skip on 2026.5.3-1 — Discord channel never starts, no error logged in the gateway log. See [Bug] @openclaw/discord 2026.5.3-1 channel still skipped silently with env-backed SecretRef token (regression of #76371; fix #76449 missed dist/ sidecar layout) #77416 for full reproduction.
  • What changed: resolvePluginContractApiPath now also searches <rootDir>/dist/ for secret-contract-api.{js,mjs,cjs,ts,mts,cts} (and contract-api.* as the secondary basename). Search order prefers dist/ when running from a built openclaw artifact (RUNNING_FROM_BUILT_ARTIFACT) and <rootDir>/ when running from source, mirroring the existing orderedContractApiExtensions heuristic. New regression test loads dist/ secret-contract-api sidecars for compiled npm-published external channel plugins materializes a real dist-layout fixture and would fail without the resolver change.
  • What did NOT change (scope boundary): No changes to the bundled-plugin contract path, the secret-target registry shape, the SecretRef resolver itself, or any plugin's runtime entry. No package.json openclaw.secretContractApi field added — the dist/ convention is inferred from existing runtimeExtensions precedent. Flat-layout external plugins (the existing test fixture at <rootDir>/secret-contract-api.cjs) keep working.

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: resolvePluginContractApiPath(rootDir) in src/secrets/channel-contract-api.ts (added by fix(secretrefs): resolve external channel contracts #76449) only checks <rootDir>/secret-contract-api.{js,mjs,cjs,ts,mts,cts} and <rootDir>/contract-api.{...}. The compiled artifact for npm-published @openclaw/<channel> plugins lives at <rootDir>/dist/secret-contract-api.js (per package.json openclaw.runtimeExtensions: ["./dist/index.js"]). Path resolver returns nullloadExternalChannelSecretContractFromRecord returns undefined → the channel's collectRuntimeConfigAssignments is never invoked → SecretRef is never queued for resolution → the runtime snapshot leaves channels.discord.token as a SecretRef object → resolveDiscordToken (which uses mode: "inspect" post-fix(secretrefs): resolve external channel contracts #76449) returns tokenStatus: "configured_unavailable"discordPlugin.config.isConfigured(account) (extensions/discord/src/shared.ts:152: Boolean(account.token?.trim())) returns false → gateway records lastError: "not configured" and never calls gateway.startAccount, so no channels/discord :: starting provider log fires.
  • Missing detection / guardrail: The existing channel-contract-api.external.test.ts fixture writes the contract module at <rootDir>/secret-contract-api.cjs (flat layout). Real npm packages bundle compiled artifacts under dist/. No test exercised the dist-layout case, so fix(secretrefs): resolve external channel contracts #76449 went green even though it would not pick up a real install.
  • Contributing context (if known): When 2026.5.2 externalized the Discord channel into @openclaw/discord, the contract loader was generalized correctly except for the artifact-location convention. Bundled plugins use a different loader (loadBundledPluginPublicArtifactModuleSync) that already knows about layout, so this only manifests for npm-installed externalized channels.

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 (added a new it("loads dist/ secret-contract-api sidecars for compiled npm-published external channel plugins", ...)).
  • Scenario the test should lock in: when an external channel plugin record's rootDir is structured like a published npm package (compiled artifacts under dist/), loadChannelSecretContractApi finds and returns the secret-contract-api module so the SecretRef resolution path can run.
  • Why this is the smallest reliable guardrail: it reuses the same makeTrackedTempDir + loadPluginMetadataSnapshotMock machinery as the existing flat-layout test, just writes the file under dist/. No live Discord/Telegram credentials, no end-to-end gateway boot needed.
  • Existing test that already covers this (if any): None — the existing flat-layout test (loads root secret-contract-api sidecars for external channel plugins) explicitly writes to <rootDir>/secret-contract-api.cjs, not <rootDir>/dist/.
  • If no new test is added, why not: N/A — new test added.

User-visible / Behavior Changes

Channel SecretRefs that round-trip through the runtime snapshot for npm-installed externalized channel plugins (currently @openclaw/discord; same shape applies to any future externalized channel) actually resolve again. Before this fix, channels.<channel>.token = { source: "env", provider: "default", id: "..." } (the docs-canonical shape) silently failed at gateway start on 2026.5.3-1; the only working configuration was to remove the SecretRef and rely on the plugin's env-fallback. After this fix, the documented SecretRef config works end-to-end again.

No config migration. No defaults change. No new fields.

Diagram (if applicable)

Before (2026.5.3-1):
channels.discord.token (SecretRef)
  -> resolvePluginContractApiPath(<rootDir>) — only checks rootDir/, file lives at rootDir/dist/
  -> returns null
  -> external contract not loaded
  -> SecretRef not queued for resolution
  -> snapshot keeps SecretRef object
  -> resolveDiscordToken(snapshot) returns tokenStatus: "configured_unavailable"
  -> isConfigured(account) === false
  -> gateway: lastError: "not configured"
  -> startAccount NEVER called, no "starting provider" log

After:
channels.discord.token (SecretRef)
  -> resolvePluginContractApiPath(<rootDir>) — checks dist/ AND rootDir/
  -> returns rootDir/dist/secret-contract-api.js
  -> external contract loaded, runtime assignment queued
  -> SecretRef resolved, snapshot mutates token to string
  -> resolveDiscordToken returns tokenStatus: "available"
  -> isConfigured(account) === true
  -> startAccount called, channel comes up

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No — same secret resolution path; this only fixes the lookup of the per-plugin contract module that declares which fields hold secrets. The openBoundaryFileSync boundary check remains unchanged: rootPath: record.rootDir, rejectHardlinks: record.origin !== "bundled", boundaryLabel: "plugin root". The added <rootDir>/dist/ candidate is strictly inside the same root boundary, so the boundary file checks still apply.
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: N/A.

Repro + Verification

Environment

  • OS: macOS 26.2 (arm64)
  • Runtime/container: Node 25.9.0 / pnpm 10.33.2
  • Model/provider: N/A (gateway-startup-only bug)
  • Integration/channel (if any): Discord (via @openclaw/discord 2026.5.3 npm install)
  • Relevant config (redacted):
    {
      "secrets": { "providers": { "default": { "source": "env" } } },
      "channels": {
        "discord": {
          "enabled": true,
          "token": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" }
        }
      }
    }
    with DISCORD_BOT_TOKEN autoloaded from ~/.openclaw/.env into the gateway's process.env.

Steps

  1. Update host to [email protected] (openclaw update from a 2026.5.2 install). Plugin sync installs @openclaw/[email protected] at ~/.openclaw/npm/node_modules/@openclaw/discord/ (compiled artifacts in dist/).
  2. Restore channels.discord.token SecretRef config above. Restart gateway: launchctl kickstart -k gui/$UID/ai.openclaw.gateway.
  3. Pre-fix: tail /tmp/openclaw/openclaw-<date>.log and run openclaw channels status. Observe absent channels/discord :: [default] starting provider line and Discord default: ... stopped, disconnected, ... error:not configured.
  4. Apply this PR (or run the new test directly: pnpm vitest run src/secrets/channel-contract-api.external.test.ts).

Expected

  • loadChannelSecretContractApi({channelId: "discord", ...}) returns the contract module from <rootDir>/dist/secret-contract-api.js.
  • Once the runtime snapshot resolves the SecretRef, gateway log shows channels/discord :: [default] starting provider (@<bot>). Channel status reports running, connected.

Actual

  • New unit test (loads dist/ secret-contract-api sidecars …) fails on main: expect(api?.secretTargetRegistryEntries).toEqual(...) receives undefined because loadExternalChannelSecretContractFromRecord returns nothing without the dist-search.
  • New unit test passes with this fix. pnpm vitest run src/secrets/channel-contract-api.external.test.ts src/secrets/runtime-external-channel-origin-discovery.test.ts src/secrets/runtime-config-collectors-channels.test.ts extensions/discord/src/setup-account-state.test.ts extensions/discord/src/account-inspect.test.ts extensions/discord/src/token.test.ts — all 25 tests across 6 files green.
  • I have not yet hot-applied the patch to my running production gateway (waiting on review); the bug-vs-fix proof is via the failing-then-passing test plus on-disk verification (<rootDir>/dist/secret-contract-api.js exists, root-level does not).

Evidence

  • Failing test/log before + passing after: see Steps above. Same test file with this commit reverted: Tests 1 failed | 2 passed (3). With this commit: Tests 3 passed (3).
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios:
    • Pre-fix: ran the new test alone — fails with the expected Received: undefined on secretTargetRegistryEntries.
    • Post-fix: ran new test + all existing channel-contract-api.external.test.ts tests + the related runtime-external-channel-origin-discovery / runtime-config-collectors-channels / discord setup-account-state / discord account-inspect / discord token tests. All green.
    • On-disk verification on a real 2026.5.3-1 install that the actual sidecar lives at ~/.openclaw/npm/node_modules/@openclaw/discord/dist/secret-contract-api.js and that no root-level sibling exists.
    • End-to-end production reproduction with the SecretRef configured (channel silently fails to start) and the workaround (no SecretRef → channel comes up via env-fallback). This is what motivated the bug report at [Bug] @openclaw/discord 2026.5.3-1 channel still skipped silently with env-backed SecretRef token (regression of #76371; fix #76449 missed dist/ sidecar layout) #77416.
  • Edge cases checked:
    • Source-mode openclaw (RUNNING_FROM_BUILT_ARTIFACT === false) still searches <rootDir>/ first so an in-tree TS source plugin would still match its secret-contract-api.ts ahead of any dist/ mirror.
    • Built-mode openclaw searches <rootDir>/dist/ first, matching production reality for npm-installed plugins.
    • Search order within each directory still preserves secret-contract-api ahead of contract-api (the legacy basename).
    • openBoundaryFileSync boundary check: the added dist/ candidate is strictly inside record.rootDir, so the existing rootPath: record.rootDir boundary check still applies. No change to hardlink rejection (rejectHardlinks: record.origin !== "bundled") or root-escape semantics.
    • Existing flat-layout test (loads root secret-contract-api sidecars …) still passes — the fix is additive, not a replacement.
  • What you did not verify: a live in-place gateway restart with this fix patched into the installed openclaw package on my host (deferred until review accepts the diff). Targeted CI lanes beyond the listed test files (broader pnpm test lane, tsgo, lint).

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: A plugin author who has both a flat-layout <rootDir>/secret-contract-api.{ext} AND a stale <rootDir>/dist/secret-contract-api.{ext} (unusual) may see different precedence depending on whether openclaw was run from source vs. built artifact.
    • Mitigation: This matches the existing RUNNING_FROM_BUILT_ARTIFACT precedence pattern in orderedContractApiExtensions. Documented in the in-file comment. Real published packages have only one location.
  • Risk: A future externalized plugin might publish artifacts under a non-dist/ directory (e.g. lib/ or out/).
    • Mitigation: Out of scope — dist/ is the convention used by every existing externalized channel plugin per package.json openclaw.runtimeExtensions: ["./dist/..."]. If a future plugin needs a different convention, an explicit package.json openclaw.secretContractEntry field (similar to the existing runtimeSetupEntry) would be the right way to support it. Filed under "future work, not now."

AI-assisted disclosure

This PR was drafted with AI assistance (Claude Code, model claude-opus-4-7-1m). The author independently verified: the on-disk plugin layout at ~/.openclaw/npm/node_modules/@openclaw/discord/, the production reproduction (channel silently fails with SecretRef, comes up with env-fallback), the failing-then-passing test, and the targeted test suite. Lightly tested locally (focused vitest lanes); broader pnpm check/pnpm test lanes and the CI matrix are deferred to GitHub Actions.

Session origin: filed via #77416 from a real production 2026.5.3-1 install hitting the silent-skip bug. The bug was confirmed by toggling the SecretRef config and observing the channel status flip between running, connected, bot:@<botname> (env-fallback) and stopped, disconnected, error:not configured (SecretRef path). Source diagnosis traced through extensions/discord/src/token.ts:resolveDiscordToken, accounts.ts:resolveDiscordAccount, shared.ts:isConfigured, src/gateway/server-channels.ts:startChannelInternal (line 442-458 gating on isConfigured), and src/secrets/channel-contract-api.ts:resolvePluginContractApiPath. Final clue: the existing test fixture (channel-contract-api.external.test.ts:31) writes a flat-layout sidecar that doesn't match real npm-package layout, masking the dist/ resolver gap.

@mogglemoss
mogglemoss requested a review from a team as a code owner May 4, 2026 16:55
@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
Review failed before ClawSweeper could summarize the requested change.

Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path.

Next step before merge
Review did not complete, so no work-lane recommendation was made.

Review details

Best possible solution:

Retry the Codex review after fixing the execution failure.

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

Unclear. The review failed before ClawSweeper could establish a reproduction path.

Is this the best way to solve the issue?

Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction.

What I checked:

  • failure reason: codex execution failed.
  • codex failure detail: Codex review failed for this PR with exit 1.
  • codex stdout: Per-item Codex failure; continuing with the rest of the shard.

Likely related people:

  • unknown: Codex failed before it could trace repository history. (role: review did not complete; confidence: low)

Remaining risk / open question:

  • No close action taken because the review did not complete.

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

@mogglemoss
mogglemoss force-pushed the fix-channel-contract-api-dist-lookup branch from 42f9ef9 to 26bdd36 Compare May 4, 2026 21:05
@vincentkoc
vincentkoc force-pushed the fix-channel-contract-api-dist-lookup branch from 26bdd36 to b1fcd0f Compare May 4, 2026 21:23
@vincentkoc

vincentkoc commented May 4, 2026

Copy link
Copy Markdown
Member

Maintainer refresh for latest origin/main.

Summary:

  • Rebased/refreshed the PR onto current origin/main (a4f2bf273a219eba218cd2e010ef7a17aa73e361).
  • Kept the fix narrow: external channel contract sidecar discovery now checks both <rootDir>/ and <rootDir>/dist/, while the existing openBoundaryFileSync plugin-root boundary check still gates the resolved path before loading.
  • Preserved the changelog entry and dist-layout regression coverage.

Verification:

  • pnpm test:serial src/secrets/channel-contract-api.external.test.ts — passed (1 file, 3 tests)
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/secrets/channel-contract-api.ts src/secrets/channel-contract-api.external.test.ts — passed
  • GitHub PR checks on refreshed head 3b6a20eed379a6fc1e48229db15559e50be140c1 — passed/skipped only

Pushed refreshed head: 3b6a20eed379a6fc1e48229db15559e50be140c1.

@vincentkoc
vincentkoc force-pushed the fix-channel-contract-api-dist-lookup branch from b1fcd0f to 8e9f5d7 Compare May 4, 2026 21:37
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.
@vincentkoc
vincentkoc force-pushed the fix-channel-contract-api-dist-lookup branch from 8e9f5d7 to 3b6a20e Compare May 4, 2026 21:38
@joshavant
joshavant merged commit 43b5df7 into openclaw:main May 4, 2026
99 checks passed
@mogglemoss
mogglemoss deleted the fix-channel-contract-api-dist-lookup branch May 5, 2026 15:34
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
…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
…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
…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
…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

Projects

None yet

3 participants