Skip to content

[Bug] @openclaw/discord 2026.5.2 channel startup crashes on SecretRef-backed token (env:default:DISCORD_BOT_TOKEN unresolved) #76371

@mogglemoss

Description

@mogglemoss

Bug type

Regression (worked in 2026.4.29, fails in 2026.5.2)

Beta release blocker

No

Summary

After upgrading to OpenClaw 2026.4.292026.5.2, the now-externalized @openclaw/discord@2026.5.2 plugin fails to start the Discord channel when channels.discord.token is configured as a SecretRef (e.g. { id: "DISCORD_BOT_TOKEN", provider: "default", source: "env" }). Telegram with the same SecretRef pattern is unaffected.

The error fires before gateway ready:

[discord] channel startup failed: channels.discord.token: unresolved SecretRef
"env:default:DISCORD_BOT_TOKEN". Resolve this command against an active gateway
runtime snapshot before reading it.

The same config worked on 2026.4.29 (bundled dist/extensions/discord).

Steps to reproduce

  1. Install OpenClaw 2026.5.2 (which auto-installs @openclaw/discord@2026.5.2 from npm via the doctor repair step).
  2. ~/.openclaw/openclaw.json:
    "channels": {
      "discord": {
        "enabled": true,
        "token": {
          "id": "DISCORD_BOT_TOKEN",
          "provider": "default",
          "source": "env"
        }
      }
    },
    "secrets": {
      "providers": { "default": { "source": "env" } }
    }
  3. Ensure DISCORD_BOT_TOKEN is in process.env (verified via dotenv autoload from ~/.openclaw/.env and directly via the LaunchAgent env file — both fail the same way).
  4. Restart the gateway.
  5. Observe [discord] channel startup failed: ... unresolved SecretRef "env:default:DISCORD_BOT_TOKEN" at startup. Channel does not come up. Across multiple restarts: same error every time, not transient.

Expected behavior

Discord channel starts using the env-resolved DISCORD_BOT_TOKEN, same as on 2026.4.29 and same as Telegram with an analogous SecretRef config on 2026.5.2.

Actual behavior

Channel startup throws synchronously inside resolveDiscordToken because normalizeDiscordToken invokes normalizeResolvedSecretInputString (strict mode), which calls assertSecretInputResolved and throws when cfg.channels.discord.token is still a SecretRef object at startup time.

Root cause analysis (from inspecting the installed plugin source)

@openclaw/discord/src/token.ts resolveDiscordTokennormalizeDiscordToken:

export function normalizeDiscordToken(raw: unknown, path: string): string | undefined {
  const trimmed = normalizeResolvedSecretInputString({ value: raw, path });
  ...
}

normalizeResolvedSecretInputString is the strict variant — it throws createUnresolvedSecretInputError ("Resolve this command against an active gateway runtime snapshot before reading it.") on unresolved SecretRefs.

Compare to @openclaw/discord/src/setup-account-state.ts inspectConfiguredToken:

function inspectConfiguredToken(value: unknown): {...} | null {
  const normalized = normalizeSecretInputString(value);   // ← safe variant; returns undefined on SecretRef
  if (normalized) { return { ..., tokenStatus: "available" }; }
  if (hasConfiguredSecretInput(value)) {
    return { ..., tokenStatus: "configured_unavailable" };
  }
  return null;
}

The inspect path correctly distinguishes "string token" / "SecretRef present but unresolved" / "absent". The startup path does not — it goes through resolveDiscordToken with the raw OpenClawConfig and crashes.

Both hits in the channel startup chain are reachable:

  • @openclaw/discord/src/accounts.ts:110resolveDiscordAccount(...) calls resolveDiscordToken(params.cfg, { accountId }).
  • @openclaw/discord/src/setup-account-state.ts:122 — falls through to resolveDiscordToken(params.cfg, { accountId }) only when inspectConfiguredToken returns null, but the throw can also originate elsewhere depending on call path.

The error message itself ("Resolve this command against an active gateway runtime snapshot before reading it.") is the explicit contract violation: the channel startup code is reading from raw config instead of from a runtime snapshot whose secrets have been pre-resolved.

This is the same family as #75433 ("source/raw channel config instead of a resolved active runtime snapshot, then calling Telegram/Discord channel discovery helpers that synchronously resolve channel credentials"), but on the channel STARTUP path rather than the embedded reply path. Sister-bug to #76369 (@openclaw/bluebubbles 2026.5.2 webhook auth crashes on SecretRef password) — same externalization-induced SecretRef regression family.

Suggested fixes (not exhaustive)

  1. In @openclaw/discord/src/token.ts — make normalizeDiscordToken use the gentle normalizeSecretInputString, and have resolveDiscordToken follow the inspectConfiguredToken pattern (treat unresolved SecretRef as "fall through to env / return none" rather than throw). This matches Telegram's behavior.
  2. In the core gateway startup — resolve channels.* SecretRef-backed tokens before invoking startChannel(plugin.id) so the runtime snapshot the plugin sees has only string tokens. This is the contract the error message implies.

(2) is the architecturally correct fix; (1) is a defensive backstop that would also prevent this class of crash in any other startup-path consumer of resolveDiscordToken.

Workarounds attempted (none effective)

  • Adding DISCORD_BOT_TOKEN to the LaunchAgent env file (~/.openclaw/service-env/ai.openclaw.gateway.env) so it is in process.env from the very first instruction — same crash. The bug is not env-availability; it is the strict SecretRef resolver throwing on the SecretRef object itself.
  • Multiple gateway restarts (launchctl kickstart -k) — same crash every time. Not a startup race, fully deterministic.
  • Setting plugins.allow explicitly to ["discord"] to silence the "non-bundled plugins may auto-load" warning — no effect on the channel startup error (different code path).

Effective workaround

Roll back to 2026.4.29 (which still ships dist/extensions/discord bundled). Discord channel starts cleanly.

Environment

  • macOS 26.2 (arm64), Node 25.9.0
  • OpenClaw 2026.5.2 (commit 8b2a6e5), installed via npm i -g openclaw
  • @openclaw/discord@2026.5.2 (auto-installed by openclaw doctor step of openclaw update)
  • Gateway managed via launchd (gui/502/ai.openclaw.gateway), local mode
  • ~/.openclaw/.env populated via dotenv (DISCORD_BOT_TOKEN, TELEGRAM_BOT_TOKEN, etc.)
  • Telegram channel with the same SecretRef pattern (channels.telegram.botToken: { id: "TELEGRAM_BOT_TOKEN", provider: "default", source: "env" }) starts and runs without issue on the same gateway boot.

Related

Metadata

Metadata

Assignees

No one assigned

    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