Skip to content

Gateway unhandled_rejection: resolveTelegramToken called against disabled exec-SecretRef accounts #76335

@maximus-dss

Description

@maximus-dss

Summary

OpenClaw 2026.4.24's gateway crashes with unhandled_rejection every ~5 minutes when channels.telegram.accounts.<name>.botToken is configured as an exec-SecretRef. The crash is caused by resolveTelegramToken being called against ALL configured telegram accounts during getRuntimeSnapshot and listEnabledTelegramAccounts, BEFORE any enabled filtering. The exec-SecretRef requires an active gateway runtime snapshot to resolve, but it's invoked during snapshot construction → throws Resolve this command against an active gateway runtime snapshot before reading it.

Volume: ~10 crashes/hour, ~250+ per day on a 7-account config. Functionality otherwise unaffected (gateway auto-restarts each time), but log noise is severe and the unhandled-rejection pattern indicates a real promise-handling bug.

Reproduction

  1. Configure channels.telegram.accounts.<name>.botToken as an exec-SecretRef:
    "channels": {
      "telegram": {
        "accounts": {
          "branson": {
            "botToken": {
              "source": "exec",
              "provider": "kc_openclaw_telegram_bottoken_branson",
              "id": "value"
            },
            "streaming": { "mode": "partial" }
          }
        }
      }
    }
  2. Start the gateway: openclaw gateway restart.
  3. Watch ~/.openclaw/logs/gateway.err.log and the stability bundle dir ~/.openclaw/logs/stability/.
  4. Within 5 minutes, observe the unhandled rejection:
    [openclaw] Unhandled promise rejection: Error: channels.telegram.accounts.branson.botToken: *** SecretRef "exec:kc_openclaw_telegram_bottoken_branson:value". Resolve this command against an active gateway runtime snapshot before reading it.
    [openclaw] wrote stability bundle: openclaw-stability-…-unhandled_rejection.json
    
  5. Crash repeats every ~5 minutes thereafter.

Smoking gun: deleting the offending account just shifts the crash to the next one

Test sequence performed:

  • t=0: branson botToken present → crashes name=branson
  • Apply jq 'del(.channels.telegram.accounts.branson.botToken)' → restart
  • t=+5min: crash now names zoe (next account in iteration order with exec-SecretRef botToken)
  • Apply jq 'del(.channels.telegram.accounts.zoe.botToken)' → restart
  • t=+5min: crash will name the next exec-SecretRef account

The bug is not specific to one account — it's the resolution path being called against any/all accounts before any enabled filter.

Code-level analysis (from inspecting the bundle)

In dist/extensions/telegram/accounts-<hash>.js:

function listEnabledTelegramAccounts(cfg) {
  return listTelegramAccountIds(cfg)
    .map((accountId) => resolveTelegramAccount({ cfg, accountId }))   // ← calls resolveTelegramToken inside
    .filter((account) => account.enabled);                              // ← filter happens AFTER
}

resolveTelegramAccount (line ~207) calls resolveTelegramToken(params.cfg, { accountId }) for every accountId listed by listTelegramAccountIds, regardless of whether that account is enabled. resolveTelegramToken then attempts to resolve the SecretRef synchronously, which fails because the runtime snapshot isn't active yet during this preflight pass.

The [SECRETS_REF_IGNORED_INACTIVE_SURFACE] log line (Telegram account is disabled or tokenFile is configured) shows openclaw KNOWS the account is disabled — but the unhandled rejection fires regardless because the resolution happens in a different code path that doesn't check enabled.

The file is byte-identical between OpenClaw 2026.4.24 and 2026.4.29 (same line numbers, same content) per local diff. Bug is present in both.

Suggested fix

Either:

  1. Filter before resolving — change listEnabledTelegramAccounts to filter by enabled from the raw account config first (cheap field read, no SecretRef resolution), then call resolveTelegramAccount only on enabled accounts.

  2. Lazy SecretRef resolution — defer resolving the SecretRef in resolveTelegramToken until actual dispatch needs the token. The preflight pass should only check the SHAPE of the config (is botToken present? is it an exec-SecretRef vs tokenFile vs literal?), not actually resolve the value.

  3. Catch + log — at minimum, wrap the resolveTelegramToken call in resolveTelegramAccount with a try/catch that logs and returns { token: "", source: "unresolved" } instead of throwing. This prevents the unhandled rejection.

Workaround we're using

Switching exec-SecretRef botTokens to tokenFile:

"branson": {
-  "botToken": {
-    "source": "exec",
-    "provider": "kc_openclaw_telegram_bottoken_branson",
-    "id": "value"
-  }
+  "tokenFile": "/Users/maximus/.openclaw/secrets/telegram/branson.token"
}

resolveTelegramToken checks accountCfg?.tokenFile?.trim() BEFORE the SecretRef resolution path, so this bypasses the bug entirely. Tradeoff: token is on disk in plaintext (mode 0600) instead of in Keychain.

Environment

  • OpenClaw: 2026.4.24 (cbcfdf6)
  • OS: macOS 15.4 (Darwin 25.4.0), M3 Ultra / 256GB
  • Affected accounts: 6 (sydney, maximus, atlas, zoe, donnie, chigurh) all use exec-SecretRef botTokens
  • Crash rate: ~10/hour, ~250+/day (24h sustained pattern)
  • Pre-existing since: 2026-04-30 evening — first stability bundles appear there

Sample stability bundle

~/.openclaw/logs/stability/openclaw-stability-2026-05-02T23-39-44-242Z-20415-unhandled_rejection.json

Bundle content is sparse (error.message/error.stack empty in the JSON) but the gateway log immediately preceding the bundle write contains the full error string with the SecretRef reference + suggestion text.

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