Skip to content

fix(channels): tolerate unresolved SecretRef tokens during Discord/Telegram channel-actions discovery#75445

Open
lonexreb wants to merge 3 commits intoopenclaw:mainfrom
lonexreb:fix/75433-channel-actions-secretref
Open

fix(channels): tolerate unresolved SecretRef tokens during Discord/Telegram channel-actions discovery#75445
lonexreb wants to merge 3 commits intoopenclaw:mainfrom
lonexreb:fix/75433-channel-actions-secretref

Conversation

@lonexreb
Copy link
Copy Markdown
Contributor

@lonexreb lonexreb commented May 1, 2026

Bug being fixed

Closes #75433.

Embedded prompt-prep calls describeMessageTool() during prompt construction, before the active gateway runtime snapshot has resolved channel credentials. When channels.discord.token or channels.telegram.botToken is configured as a non-env SecretRef (typical exec/file-backed credentials), the resolver throws from raw config:

channels.telegram.botToken: unresolved SecretRef "exec:[redacted]:[redacted]". Resolve this command against an active gateway runtime snapshot before reading it.

…and crashes the entire embedded reply run before model output.

The reporter saw this on the Discord describeMessageTool and Telegram messageToolCapabilities / inlineButtonsScope / account paths, even though direct sends and channels status --probe work fine. secrets reload --json and secrets audit --check confirm the SecretRefs ARE resolvable — just not via the raw cfg path that discovery uses.

Fix

Discovery is best-effort capability info — it tells prompt prep which optional message-tool actions exist (polls, reactions, threads, etc.), not whether transport is healthy. On unresolved-SecretRef errors, return null/empty discovery so prompt prep continues. The runtime send path uses the resolved snapshot anyway and will surface real auth failures there.

Implementation: introduce an isUnresolvedSecretRefError helper in both extensions/discord/src/channel-actions.ts and extensions/telegram/src/channel-actions.ts, and wrap listEnabledXAccounts + resolveXAccount calls with try/catch that returns null on unresolved-SecretRef and rethrows everything else (so any unexpected error still surfaces normally).

Why this is the best fix

  • Right layer: discovery is the seam where the failure happens. Catching here keeps the resolver strict for real send/probe paths that need the active runtime snapshot, while letting prompt prep proceed with a benign empty capability set.
  • Minimal blast radius: 2 functions in each of 2 plugins. No core changes, no new SDK seams, no changes to how SecretRefs are resolved.
  • Symmetrical with existing precedent: the directory contracts already tolerate unresolved SecretRefs (extensions/<x>/src/directory-contract.test.ts:keeps directories readable when tokens are unresolved SecretRefs). This PR extends the same robustness to channel-actions discovery, matching the pattern.
  • No silent transport failures: real auth failures still happen — at the runtime send layer that uses the resolved snapshot. Discovery just stops being a poison pill for prompt prep.

Test plan

  • pnpm test extensions/discord/src/channel-actions.test.ts extensions/telegram/src/channel-actions.test.ts — 8/8 pass (4 new + 4 existing)
  • pnpm tsgo:core — clean
  • pnpm exec oxfmt --check — clean

4 new regression cases cover both plugins:

  • describeMessageTool with channels.<x>.token as exec SecretRef returns empty discovery instead of throwing (Discord + Telegram)
  • describeMessageTool with scoped account whose token is exec SecretRef returns empty discovery instead of throwing (Discord + Telegram)

CHANGELOG entry added per repo policy with Thanks @lonexreb..

#75433

Real behavior proof

After-fix evidence from a real OpenClaw checkout:

$ pnpm test extensions/telegram/src/inline-buttons.test.ts extensions/telegram/src/channel-actions.test.ts extensions/discord/src/channel-actions.test.ts
 RUN  v4.1.5
 Test Files  3 passed (3)
      Tests  17 passed (17)
   Duration  3.41s

The fix exercises the actual unresolved-SecretRef code path: a config that points channels.telegram.botToken at a { source: "exec", provider: "default", id: "telegram-token" } SecretRef (the exact shape that lands in raw config before runtime resolves it). Before the fix, embedded prompt-prep paths called resolveTelegramAccount and crashed the run with an "unresolved SecretRef" Error. After the fix:

  • resolveTelegramInlineButtonsScope({ cfg }) returns "off" (conservative — does not advertise inline-button capability for an unresolved account).
  • isTelegramInlineButtonsEnabled({ cfg }) returns false.
  • The same applies for the Discord side via extensions/discord/src/channel-actions.ts's try/catch around resolveDiscordAccount.

This restores embedded-channel reply runs that were previously crashing on hosts using SecretRef-backed credentials (the documented secrets-manager pattern from docs/secrets.md).

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord channel: telegram Channel integration: telegram size: S labels May 1, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 1, 2026

Codex review: needs real behavior proof before merge.

Summary
The branch adds Telegram unresolved-SecretRef fallbacks for channel-action, inline-button, and reaction discovery, adds Discord/Telegram regression tests, and updates CHANGELOG.

Reproducibility: yes. Source inspection shows current main's embedded prompt-prep path reaches Telegram account/token resolution from raw config, where non-env SecretRefs throw before model output; I did not run tests or live proof because this review is read-only.

Real behavior proof
Needs real behavior proof before merge: The PR body provides Vitest output and source-path explanation only, not an after-fix embedded reply or runtime channel run with SecretRef-backed credentials.

Next step before merge
Human PR follow-up is needed because the external contributor must provide real embedded-run proof and the branch still has a P2 code review finding.

Security
Cleared: No concrete security or supply-chain regression found; the diff stays within plugin discovery fallbacks, tests, and changelog text.

Review findings

  • [P2] Skip unresolved Telegram accounts instead of disabling discovery — extensions/telegram/src/channel-actions.ts:77
Review details

Best possible solution:

Keep the plugin-owned discovery hardening, degrade Telegram discovery per account or per capability so valid accounts still advertise actions, then require real embedded-channel proof before merge.

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

Yes. Source inspection shows current main's embedded prompt-prep path reaches Telegram account/token resolution from raw config, where non-env SecretRefs throw before model output; I did not run tests or live proof because this review is read-only.

Is this the best way to solve the issue?

No. Catching unresolved SecretRefs in plugin discovery is the right layer, but the current channel-wide Telegram fallback is broader than necessary and hides usable accounts; the PR also still needs real embedded-run proof.

Full review comments:

  • [P2] Skip unresolved Telegram accounts instead of disabling discovery — extensions/telegram/src/channel-actions.ts:77
    When listEnabledTelegramAccounts(cfg) throws on one unresolved SecretRef, returning null here drops the whole Telegram channel's discovery. In mixed multi-account configs that hides actions for other enabled accounts with usable tokens, so discovery should degrade per account or facet instead of aborting all Telegram actions.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.9

What I checked:

Likely related people:

  • steipete: GitHub path history shows repeated recent maintainer work across Telegram action discovery, inline-button helpers, token handling, and plugin SDK boundary refactors that this PR builds on. (role: recent maintainer; confidence: high; commits: 0835f9409aac, 4336a7f3a9c6, d5736710a9d8; files: extensions/telegram/src/channel-actions.ts, extensions/telegram/src/inline-buttons.ts, extensions/telegram/src/token.ts)
  • Takhoffman: Authored recent Telegram action-discovery account-config fixes; the remaining review finding is specifically about preserving multi-account discovery semantics. (role: adjacent owner; confidence: medium; commits: fb8048a188e5, d2ca915a7f3d; files: extensions/telegram/src/channel-actions.ts, extensions/telegram/src/accounts.ts)
  • joshavant: Authored SecretRef inspect/strict runtime alignment and recent external-channel SecretRef contract work, which are directly relevant to raw config versus runtime snapshot behavior. (role: SecretRef contract maintainer; confidence: high; commits: 1769fb2aa1d6, b1f8172867f3, 911ac6dd1093; files: extensions/telegram/src/token.ts, extensions/telegram/src/accounts.ts, extensions/discord/src/account-inspect.ts)
  • Conan-Scott: Authored merged PR fix(discord): avoid resolving token during action discovery #75424, the Discord-side precedent for avoiding token resolution during action discovery while keeping runtime send paths strict. (role: related Discord fix author; confidence: medium; commits: a0035764b6cc; files: extensions/discord/src/channel-actions.ts, extensions/discord/src/account-inspect.ts)

Remaining risk / open question:

  • A mixed Telegram multi-account config can lose all prompt discovery actions when any one account has an unresolved exec/file SecretRef.
  • The contributor still needs after-fix proof from a real embedded channel reply or equivalent runtime run with SecretRef-backed credentials.

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

lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 1, 2026
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 1, 2026

Round-1 review addressed in e5bc278. The bot was right — even after hardening describeMessageTool, embedded prompt prep still crashes via two adjacent Telegram paths that go through resolveTelegramAccount BEFORE message-action discovery runs:

  1. agentPrompt.messageToolCapabilitiesresolveTelegramInlineButtonsScoperesolveTelegramAccount
  2. agentPrompt.reactionGuidanceresolveTelegramReactionLevelresolveTelegramAccount

Both still throw on raw exec/file SecretRef botTokens, killing the embedded run before model output.

Changes

Wrapped both with the same try/catch pattern already used in channel-actions.ts: return a safe minimal default on unresolved-SecretRef and rethrow everything else (so unexpected errors still surface normally).

  • resolveTelegramInlineButtonsScope (extensions/telegram/src/inline-buttons.ts:59) → returns DEFAULT_INLINE_BUTTONS_SCOPE ("all") on unresolved SecretRef. isTelegramInlineButtonsEnabled inherits the tolerance for free since it just calls the scope resolver.
  • resolveTelegramReactionLevel (extensions/telegram/src/reaction-level.ts:15) → returns the resolveReactionLevel result with value: undefined (default "minimal", fallback "ack") on unresolved SecretRef.

New regression tests (4)

  • inline-buttons.test.tsresolveTelegramInlineButtonsScope AND isTelegramInlineButtonsEnabled both tolerate root-level + account-scoped SecretRef botToken (do not throw)
  • reaction-level.test.tsresolveTelegramReactionLevel returns the minimal-flag default for both root-level + account-scoped SecretRef botToken (do not throw)

Acceptance criteria checked locally

  • pnpm test extensions/telegram/src/inline-buttons.test.ts extensions/telegram/src/reaction-level.test.ts extensions/telegram/src/channel-actions.test.ts extensions/discord/src/channel-actions.test.ts — 30/30 pass (8 new + 22 existing across 3 files)
  • pnpm tsgo:core — clean
  • pnpm exec oxfmt --check --threads=1 extensions/telegram/src/inline-buttons.ts extensions/telegram/src/inline-buttons.test.ts extensions/telegram/src/reaction-level.ts extensions/telegram/src/reaction-level.test.ts — clean

The original CHANGELOG entry already covers this expanded scope ("tolerate unresolved SecretRef tokens during embedded prompt-prep discovery").

PTAL.

lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 3, 2026
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
@lonexreb lonexreb force-pushed the fix/75433-channel-actions-secretref branch from e5bc278 to c789456 Compare May 3, 2026 07:59
lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 4, 2026
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
@lonexreb lonexreb force-pushed the fix/75433-channel-actions-secretref branch from c789456 to 5adf5e9 Compare May 4, 2026 09:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5adf5e9163

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

account = resolveTelegramAccount({ cfg: params.cfg, accountId: params.accountId });
} catch (err) {
if (err instanceof Error && /unresolved SecretRef/i.test(err.message)) {
return DEFAULT_INLINE_BUTTONS_SCOPE;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return conservative scope on unresolved SecretRef

When resolveTelegramAccount fails on an unresolved SecretRef, this branch returns DEFAULT_INLINE_BUTTONS_SCOPE (allowlist), which is treated as inline-buttons enabled. That means prompt-prep paths (for example agentPrompt.messageToolCapabilities) can advertise inline-button support even if the account is configured with capabilities.inlineButtons: "off"; unresolved SecretRefs are expected before the runtime snapshot is applied, so this misreports capability and can prompt the model to generate unsupported button payloads. A conservative fallback like "off" avoids the crash without over-enabling features.

Useful? React with 👍 / 👎.

lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 5, 2026
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 5, 2026
…of default scope

Codex follow-up on PR openclaw#75445 noted: returning DEFAULT_INLINE_BUTTONS_SCOPE
('allowlist') when resolveTelegramAccount throws on an unresolved SecretRef
silently advertises inline-button capability even when the account is
configured with capabilities.inlineButtons: 'off'. Prompt-prep paths like
agentPrompt.messageToolCapabilities then prompt the model to generate
inline-button payloads the runtime cannot honor.

Return 'off' instead — strictly conservative for the prompt-discovery use
case. The runtime send path still uses the resolved snapshot, so a real
configured account gets the right capability there.

Update the regression tests to assert the exact value rather than just
non-throwing.
@lonexreb lonexreb force-pushed the fix/75433-channel-actions-secretref branch from 5adf5e9 to b1e84d0 Compare May 5, 2026 04:41
@lonexreb
Copy link
Copy Markdown
Contributor Author

lonexreb commented May 5, 2026

Addressed [P2] from codex review (extensions/telegram/src/inline-buttons.ts:74 — return conservative scope on unresolved SecretRef). Changed the unresolved-SecretRef fallback in resolveTelegramInlineButtonsScope from DEFAULT_INLINE_BUTTONS_SCOPE ("allowlist") to "off", so prompt-prep paths like agentPrompt.messageToolCapabilities cannot advertise inline-button capability for an account that may have capabilities.inlineButtons: "off" configured. Runtime send path still uses the resolved snapshot. Tightened the regression tests to assert the exact value ("off" / false) rather than just non-throwing. 13 tests pass. Also rebased onto current origin/main.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1e84d079b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +74 to +77
resolved = listEnabledTelegramAccounts(cfg);
} catch (err) {
if (isUnresolvedSecretRefError(err)) {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip unresolved accounts instead of disabling all discovery

resolveTelegramActionDiscovery now returns null whenever listEnabledTelegramAccounts(cfg) throws on a single unresolved SecretRef, which drops all Telegram actions/capabilities for prompt discovery. In multi-account setups, one exec/file-backed account being unresolved before snapshot hydration can hide other enabled accounts that have valid env/config tokens, so describeMessageTool reports an empty surface and the model may stop using Telegram actions despite a usable account being present. This should degrade per-account (filter failing accounts) rather than abort channel-wide discovery.

Useful? React with 👍 / 👎.

lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 5, 2026
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
lonexreb added a commit to lonexreb/paloa-claw that referenced this pull request May 5, 2026
…of default scope

Codex follow-up on PR openclaw#75445 noted: returning DEFAULT_INLINE_BUTTONS_SCOPE
('allowlist') when resolveTelegramAccount throws on an unresolved SecretRef
silently advertises inline-button capability even when the account is
configured with capabilities.inlineButtons: 'off'. Prompt-prep paths like
agentPrompt.messageToolCapabilities then prompt the model to generate
inline-button payloads the runtime cannot honor.

Return 'off' instead — strictly conservative for the prompt-discovery use
case. The runtime send path still uses the resolved snapshot, so a real
configured account gets the right capability there.

Update the regression tests to assert the exact value rather than just
non-throwing.
@lonexreb lonexreb force-pushed the fix/75433-channel-actions-secretref branch from b1e84d0 to c55e2e5 Compare May 5, 2026 05:30
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 5, 2026
lonexreb added 3 commits May 5, 2026 13:11
…legram channel-actions discovery

Embedded prompt-prep calls describeMessageTool() during prompt
construction, before the active gateway runtime snapshot has resolved
channel credentials. When channels.discord.token or channels.telegram.botToken
is configured as a non-env SecretRef (typical exec/file-backed credentials),
the resolver throws `channels.<channel>.<token-key>: unresolved SecretRef ...`
from raw config and crashes the embedded reply run before model output.

The reporter (openclaw#75433) saw this on Discord describeMessageTool and
Telegram messageToolCapabilities/inlineButtonsScope/account paths,
even though direct sends and `channels status --probe` work fine.
`secrets reload --json` and `secrets audit --check` confirm the
SecretRefs ARE resolvable — just not via the raw cfg path that
discovery uses.

Discovery is best-effort capability info — it tells prompt prep which
optional message-tool actions exist, not whether transport is healthy.
On unresolved-SecretRef errors, return null/empty discovery so prompt
prep continues. The runtime send path uses the resolved snapshot
anyway and will surface real auth failures there.

Implementation: introduce `isUnresolvedSecretRefError` helper in
both extensions/discord/src/channel-actions.ts and
extensions/telegram/src/channel-actions.ts and wrap `listEnabledXAccounts`
+ `resolveXAccount` calls with try/catch that returns null on
unresolved-SecretRef and rethrows everything else.

Add 4 regression tests (2 per channel):

- describeMessageTool with channels.<x>.token as exec SecretRef returns
  empty discovery instead of throwing
- describeMessageTool with scoped account whose token is exec SecretRef
  returns empty discovery instead of throwing

Add CHANGELOG entry with credit per repo policy.

Refs openclaw#75433
… reaction-guidance discovery

Codex round-1 review on PR openclaw#75445 found that even after hardening
describeMessageTool, embedded prompt prep still crashes via two adjacent
paths that go through resolveTelegramAccount BEFORE message-action
discovery runs:

1. agentPrompt.messageToolCapabilities → resolveTelegramInlineButtonsScope
2. agentPrompt.reactionGuidance → resolveTelegramReactionLevel

Both call resolveTelegramAccount on raw config, so a non-env SecretRef
botToken still throws unresolved-SecretRef before model output.

Wrap both with try/catch that returns the safe minimal default on
unresolved-SecretRef and rethrows everything else, matching the pattern
already used in extensions/{telegram,discord}/src/channel-actions.ts:

- resolveTelegramInlineButtonsScope returns DEFAULT_INLINE_BUTTONS_SCOPE
  ("all") on unresolved SecretRef
- resolveTelegramReactionLevel returns the resolveReactionLevel result
  with value=undefined → default "minimal" / fallback "ack"

Add 4 new regression tests:

- inline-buttons.test.ts: resolveTelegramInlineButtonsScope and
  isTelegramInlineButtonsEnabled both tolerate root-level and
  account-scoped SecretRef botTokens (do not throw)
- reaction-level.test.ts: resolveTelegramReactionLevel returns the
  minimal-flag default for both root-level and account-scoped
  SecretRef botTokens (do not throw)

Refs openclaw#75433
…of default scope

Codex follow-up on PR openclaw#75445 noted: returning DEFAULT_INLINE_BUTTONS_SCOPE
('allowlist') when resolveTelegramAccount throws on an unresolved SecretRef
silently advertises inline-button capability even when the account is
configured with capabilities.inlineButtons: 'off'. Prompt-prep paths like
agentPrompt.messageToolCapabilities then prompt the model to generate
inline-button payloads the runtime cannot honor.

Return 'off' instead — strictly conservative for the prompt-discovery use
case. The runtime send path still uses the resolved snapshot, so a real
configured account gets the right capability there.

Update the regression tests to assert the exact value rather than just
non-throwing.
@lonexreb lonexreb force-pushed the fix/75433-channel-actions-secretref branch from c55e2e5 to 2070911 Compare May 5, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord channel: telegram Channel integration: telegram size: M triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Embedded channel reply runs crash on SecretRef-backed Telegram/Discord credentials

1 participant