fix(channels): tolerate unresolved SecretRef tokens during Discord/Telegram channel-actions discovery#75445
Conversation
|
Codex review: needs real behavior proof before merge. Summary 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 Next step before merge Security Review findings
Review detailsBest 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:
Overall correctness: patch is incorrect What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 5fae1c32b5f8. |
… 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
|
Round-1 review addressed in e5bc278. The bot was right — even after hardening
Both still throw on raw exec/file SecretRef botTokens, killing the embedded run before model output. ChangesWrapped both with the same
New regression tests (4)
Acceptance criteria checked locally
The original CHANGELOG entry already covers this expanded scope ("tolerate unresolved SecretRef tokens during embedded prompt-prep discovery"). PTAL. |
… 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
e5bc278 to
c789456
Compare
… 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
c789456 to
5adf5e9
Compare
There was a problem hiding this comment.
💡 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; |
There was a problem hiding this comment.
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 👍 / 👎.
… 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.
5adf5e9 to
b1e84d0
Compare
|
Addressed [P2] from codex review ( |
There was a problem hiding this comment.
💡 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".
| resolved = listEnabledTelegramAccounts(cfg); | ||
| } catch (err) { | ||
| if (isUnresolvedSecretRefError(err)) { | ||
| return null; |
There was a problem hiding this comment.
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 👍 / 👎.
… 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.
b1e84d0 to
c55e2e5
Compare
…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.
c55e2e5 to
2070911
Compare
Bug being fixed
Closes #75433.
Embedded prompt-prep calls
describeMessageTool()during prompt construction, before the active gateway runtime snapshot has resolved channel credentials. Whenchannels.discord.tokenorchannels.telegram.botTokenis configured as a non-env SecretRef (typical exec/file-backed credentials), the resolver throws from raw config:…and crashes the entire embedded reply run before model output.
The reporter saw this on the Discord
describeMessageTooland TelegrammessageToolCapabilities/inlineButtonsScope/ account paths, even though direct sends andchannels status --probework fine.secrets reload --jsonandsecrets audit --checkconfirm 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
isUnresolvedSecretRefErrorhelper in bothextensions/discord/src/channel-actions.tsandextensions/telegram/src/channel-actions.ts, and wraplistEnabledXAccounts+resolveXAccountcalls withtry/catchthat returns null on unresolved-SecretRef and rethrows everything else (so any unexpected error still surfaces normally).Why this is the best fix
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.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— cleanpnpm exec oxfmt --check— clean4 new regression cases cover both plugins:
describeMessageToolwithchannels.<x>.tokenas exec SecretRef returns empty discovery instead of throwing (Discord + Telegram)describeMessageToolwith 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:
The fix exercises the actual unresolved-SecretRef code path: a config that points
channels.telegram.botTokenat 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 calledresolveTelegramAccountand 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 })returnsfalse.extensions/discord/src/channel-actions.ts's try/catch aroundresolveDiscordAccount.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).