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

Description

@mogglemoss

Bug

@openclaw/[email protected] (shipped as [email protected] 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. [email protected] installed (npm view openclaw version2026.5.3-1); @openclaw/[email protected] 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/[email protected] 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

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions