Skip to content

[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

@mogglemoss

Description

@mogglemoss

Bug

@openclaw/discord@2026.5.3 (shipped as openclaw@2026.5.3-1 on npm) still fails to start the Discord channel when channels.discord.token is configured as an env-backed SecretRef, despite issue #76371 being closed by merged PR #76449 ("fix(secretrefs): resolve external channel contracts").

Failure mode shifted from the explicit crash on 2026.5.2 (unresolved SecretRef "env:default:DISCORD_BOT_TOKEN") to a silent skip: the plugin loads, no error is logged, but the Discord channel never starts and shows error: not configured in openclaw channels status.

Reproduction (clean)

  1. openclaw@2026.5.3-1 installed (npm view openclaw version2026.5.3-1); @openclaw/discord@2026.5.3 plugin installed at ~/.openclaw/npm/node_modules/@openclaw/discord/.
  2. ~/.openclaw/openclaw.json includes:
    {
      "secrets": { "providers": { "default": { "source": "env" } } },
      "channels": {
        "discord": {
          "enabled": true,
          "token": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" }
        }
      }
    }
  3. DISCORD_BOT_TOKEN is set in the gateway service's env (via ~/.openclaw/.env autoload).
  4. Restart the gateway: launchctl kickstart -k gui/$UID/ai.openclaw.gateway.

Expected (per #76449)

Discord channel starts. Log shows channels/discord :: [default] starting provider (@<bot>).

Actual

  • No starting provider log line. Plugin is loaded (counted in 9 plugins: ...; discord; ...) but channel never starts.
  • openclaw channels status shows: Discord default: enabled, configured, secret unavailable in this command path, stopped, disconnected, token:config (unavailable), health:not-running, error:not configured
  • openclaw status --deep channels table: Discord │ ON │ WARN │ configured token unavailable in this command path
  • Removing the channels.discord.token block (env-fallback only) → channel comes up immediately. So the trigger is the SecretRef config exactly as documented.

Root cause

PR #76449 added an external-plugin secret-contract-api loader at src/secrets/channel-contract-api.ts. Its path resolver, resolvePluginContractApiPath(rootDir), only checks <rootDir>/secret-contract-api.{js,mjs,cjs,ts,mts,cts} and <rootDir>/contract-api.{...}.

Real npm-published externalized channel plugins put compiled artifacts under <rootDir>/dist/ (per package.json openclaw.runtimeExtensions: ["./dist/index.js"]). For example, the actual sidecar in the installed Discord plugin lives at:

~/.openclaw/npm/node_modules/@openclaw/discord/dist/secret-contract-api.js

But the resolver only looks in:

~/.openclaw/npm/node_modules/@openclaw/discord/secret-contract-api.{js,mjs,cjs,ts,mts,cts}    (none of these exist)

Returns nullloadExternalChannelSecretContractFromRecord returns undefinedcollectChannelConfigAssignments skips Discord → no SecretRef assignment queued → runtime snapshot keeps channels.discord.token as the unresolved SecretRef object.

Downstream: selectDiscordRuntimeConfig returns the snapshot (same SecretRef value), resolveDiscordToken calls resolveSecretInputString({ mode: "inspect" }) on the still-unresolved SecretRef → returns configured_unavailableaccount.token is "", account.tokenStatus is configured_unavailablediscordPlugin.config.isConfigured(account) returns false (extensions/discord/src/shared.ts:152: Boolean(account.token?.trim())) → gateway sets lastError: "not configured" and never calls gateway.startAccount.

The existing PR #76449 test fixture (src/secrets/channel-contract-api.external.test.ts) writes a flat-layout sidecar at <rootDir>/secret-contract-api.cjs, which masks the bug because no real-world npm package layout was exercised.

Why the failure mode is silent

Result: error:not configured only surfaces if you explicitly query openclaw channels status.

Fix

Make resolvePluginContractApiPath also check <rootDir>/dist/ for compiled npm-published externalized channel plugins. PR submitting now.

Workaround

Remove channels.discord.token from openclaw.json and rely on the env-fallback inside the Discord plugin (process.env.DISCORD_BOT_TOKEN). This bypasses the SecretRef path entirely, which is documented as supported per extensions/discord/src/doctor.ts and docs/channels/discord.md.

Environment

  • OS: macOS 26.2 (arm64), Node 25.9.0
  • OpenClaw: 2026.5.3-1 (npm global, LaunchAgent-managed gateway)
  • Plugin: @openclaw/discord@2026.5.3 installed at ~/.openclaw/npm/node_modules/@openclaw/discord/
  • Build artifact verified on disk: dist/secret-contract-api.js exists, root-level secret-contract-api.* does not.

Disclosure

This bug report and the accompanying PR were drafted with AI assistance (Claude Code, model claude-opus-4-7) using interactive code review of the installed plugin source and the cloned openclaw/openclaw main branch. The author has independently verified the on-disk paths and the production behavior, and has tested the fix locally against src/secrets/channel-contract-api.external.test.ts.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions