Skip to content

fix(slack): tolerate unresolved channel SecretRef on outbound send path#68954

Merged
openperf merged 2 commits into
openclaw:mainfrom
openperf:fix/slack-send-tolerate-unresolved-secret-ref-68237
Apr 20, 2026
Merged

fix(slack): tolerate unresolved channel SecretRef on outbound send path#68954
openperf merged 2 commits into
openclaw:mainfrom
openperf:fix/slack-send-tolerate-unresolved-secret-ref-68237

Conversation

@openperf

@openperf openperf commented Apr 19, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: On runtimes where the cfg snapshot returned by loadConfig() retains an unresolved SecretRef object on channels.slack.accounts.<id>.botToken, every Slack outbound reply throws channels.slack.accounts.<id>.botToken: unresolved SecretRef "<source>:<provider>:<id>". Resolve this command against an active gateway runtime snapshot before reading it. The error is thrown deep inside sendMessageSlack (extensions/slack/src/send.ts:325) before the explicit, boot-resolved opts.token is ever consulted at extensions/slack/src/send.ts:329. Reactions (reactions.add) and inbound dispatch keep working because they reuse the boot-time ctx.app.client and never re-enter sendMessageSlack. Independently confirmed by three reporters across file-source secrets, exec-source secrets (1Password CLI wrapper, macOS arm64), and the original ticket.

  • Root Cause: sendMessageSlack calls resolveSlackAccount({ cfg }) with cfg = opts.cfg ?? loadConfig(). resolveSlackAccount (extensions/slack/src/accounts.ts:61-72) eagerly invokes the strict resolvers resolveSlackBotToken / resolveSlackAppToken / resolveSlackUserToken on merged.botToken / appToken / userToken. Those resolvers route through assertSecretInputResolved (src/config/types.secrets.ts:137-140), which throws on any SecretRef object that survived in the runtime snapshot. The strict throw is correct at boot-time provider initialization (where there is no fallback) but is a false positive on the send path because sendMessageSlack already has a boot-resolved explicit override (opts.token, threaded from ctx.botToken) and only consults account.botToken as a defensive fallback. The upstream condition under which the cfg snapshot ends up holding an unresolved SecretRef (rather than the resolved string the secrets-runtime activation produces at boot) is environment-dependent and not fully nailed down here — likely candidates include finalizeRuntimeSnapshotWrite re-pinning the raw cfg when a refresh handler returns false, transient secret-provider failures during a config-write reload, and OAuth token refreshes that touch auth.profiles.* and trigger a snapshot reload. The reporters' A/B between 2026.4.14 and 2026.4.15 is deterministic on long-running gateways with active OAuth profiles or external secret providers; the fix here is intentionally scoped to the slack-send consumer side and does not attempt to address the wider snapshot-pollution path.

  • Fix: Add an opt-in tolerateUnresolvedSecrets flag to resolveSlackAccount. When set, the function reads botToken / appToken / userToken through normalizeSecretInputString (gentle: returns undefined for SecretRef objects) instead of the strict resolvers (which throw). sendMessageSlack opts in to the tolerant read, with an inline comment explaining why: the explicit opts.token covers the actual auth, and the existing if (!fallback) throw "Slack bot token missing for account ..." in resolveToken still emits a clean, actionable error if neither explicit nor fallback is available. The flag is opt-in only so all other callers (provider boot in extensions/slack/src/monitor/provider.ts:271, listEnabledSlackAccounts, account inspection helpers, etc.) preserve the strict behavior — boot-time providers without an override still surface unresolved channel SecretRefs loudly. This addresses the failure at the consumer side directly: regardless of why the snapshot holds an unresolved SecretRef, sendMessageSlack no longer fails when it has a valid explicit token.

  • What changed:

    • extensions/slack/src/accounts.ts: add tolerateUnresolvedSecrets?: boolean parameter to resolveSlackAccount; gate the per-account credential reads on the flag, using normalizeSecretInputString (from openclaw/plugin-sdk/secret-input) when set; documented the contract on the new parameter. Per-credential env-var fallback gating (isSecretRef(merged.<field>)) ensures that a configured-but-unresolved SecretRef cannot be silently overridden by a stray SLACK_*_TOKEN process env var, while credentials that are unset entirely still allow env fallback so legitimate env-only setups (no per-account config token) keep working. Also, in the same file's mergeSlackAccountConfig helper, drop the redundant | undefined constituents from the two existing as ... casts on cfg.channels?.slack / cfg.channels?.slack?.accounts — optional chaining already produces T | undefined, so the explicit | undefined adds nothing while triggering typescript-eslint(no-redundant-type-constituents) once accounts.ts enters the staged set. No runtime behavior change; the receiving resolveMergedAccountConfig parameter type is TConfig | undefined so the narrower cast remains assignable.
    • extensions/slack/src/send.ts: sendMessageSlack now passes tolerateUnresolvedSecrets: true when resolving the account snapshot, with an inline comment explaining why the tolerant read is safe here.
    • extensions/slack/src/accounts.test.ts: extend the existing test file with a focused describe("resolveSlackAccount tolerateUnresolvedSecrets", …) block (6 cases) — strict-by-default still throws on unresolved SecretRef; tolerant-mode returns undefined credentials with botTokenSource: "none" while preserving non-credential account info; tolerant-mode preserves resolved string credentials unchanged; CWE-287 guard blocks SLACK_*_TOKEN env fallback when all three credentials are configured as SecretRef; env-only setups (no config token) continue to use SLACK_*_TOKEN env fallback; per-credential isolation (unresolved SecretRef on botToken does not affect SLACK_APP_TOKEN fallback when appToken is unset). The cfgWithUnresolvedBotTokenRef fixture is cast through unknown to OpenClawConfig (with a comment explaining why), because SlackAccountConfig.botToken is statically typed as string while the runtime snapshot can still hold an unresolved SecretRef object — exactly the shape this PR is fixing.
  • What did NOT change (scope boundary):

    • No change to assertSecretInputResolved, normalizeResolvedSecretInputString, normalizeSecretInputString, or any other src/config/types.secrets.ts semantics. The strict / inspect contract is preserved.
    • No change to the secrets runtime snapshot (src/secrets/runtime.ts), loadConfig, loadPinnedRuntimeConfig, setRuntimeConfigSnapshot, or finalizeRuntimeSnapshotWrite. The wider snapshot-pollution path is intentionally out of scope and belongs in a separate change.
    • No change to the reply runner or agent runner modules. The existing scoped resolution design is left intact.
    • No change to resolveSlackBotToken / resolveSlackAppToken / resolveSlackUserToken (extensions/slack/src/token.ts); the strict resolvers are still the default for every caller that does not opt into tolerant mode (provider boot, listEnabledSlackAccounts, account inspection helpers).
    • The mergeSlackAccountConfig cleanup is type-only (drops a redundant union constituent); it does not change the function signature, return type, runtime behavior, or callers' contract.
    • No change to other channel extensions (Discord, Telegram, WhatsApp, Feishu, Matrix, etc.), to the gateway, to the Plugin SDK contract, to public types exported from openclaw/plugin-sdk/*, to channel-config zod schemas, or to any documentation. CHANGELOG entry intentionally omitted from this patch and can be added on merge per maintainer preference.
    • The new option is a backwards-compatible additive parameter; existing callers continue to pass { cfg, accountId } and get the existing strict behavior.

Reproduction

The failure mode at the slack send path is deterministic — when loadConfig() returns a cfg with a SecretRef object on channels.slack.accounts.<id>.botToken, resolveSlackAccount strict-throws the exact error message in the issue. What is environment-dependent is the upstream condition that causes the runtime snapshot to retain that unresolved SecretRef rather than the resolved string the secrets-runtime activation produces at boot. There is no single one-line minimal repro that triggers the snapshot-pollution path independently of the real-world setups in the issue thread; the verification paths below cover both "did the fix change the consumer-side behavior correctly" and "does the fix unblock the reported scenario end-to-end."

Path A — direct contract verification (most reliable, environment-independent)

The 6 new cases in extensions/slack/src/accounts.test.ts pin the new contract directly without needing to reproduce the snapshot-pollution upstream:

  1. Strict mode (default, no flag) still throws on a SecretRef-shaped cfg with the path-tagged error message.
  2. Tolerant mode returns undefined credentials with botTokenSource: "none" for unresolved SecretRef while preserving non-credential account info (accountId, allowFrom, etc.).
  3. Tolerant mode preserves already-resolved string credentials unchanged.
  4. CWE-287 guard: when all three credentials are configured as SecretRef, SLACK_*_TOKEN env fallbacks are all blocked in tolerant mode.
  5. Env-only setups (no config token configured at all) continue to use SLACK_*_TOKEN env fallback in tolerant mode.
  6. Per-credential isolation: an unresolved SecretRef on botToken blocks SLACK_BOT_TOKEN only; SLACK_APP_TOKEN still falls back when appToken is unset.

Run with: pnpm test extensions/slack/src/accounts.test.ts.

Path B — end-to-end on a long-running gateway (matches the reporters' setups)

  1. Configure Slack on 2026.4.15 with the bot token sourced through a SecretRef, for example:

    // openclaw.json
    "channels": {
      "slack": {
        "mode": "socket",
        "accounts": {
          "default": {
            "botToken": { "source": "exec", "provider": "default", "id": "slack_bot_token" },
            "appToken": { "source": "exec", "provider": "default", "id": "slack_app_token" }
          }
        }
      }
    }
  2. Boot the gateway with a working secret provider (the secrets-runtime activation will resolve botToken into the snapshot at boot, so the very first reply succeeds).

  3. Trigger a config-write reload that exercises the snapshot-pollution upstream — for example, let an OAuth token refresh write to auth.profiles.*, run touch ~/.openclaw/openclaw.json, or invoke a UI / wizard flow that bumps meta.lastTouchedAt. (@oclilbuddy's repro used exec-source 1Password CLI which exhibits this on its own through transient resolver delays.)

  4. Send a message after the reload settles. On 2026.4.15 the reply now throws the unresolved-SecretRef error; the typing reaction (reactions.add) still succeeds, exposing the asymmetry between the boot-resolved ctx.app.client (works) and the loadConfig()-driven sendMessageSlack path (fails).

  5. With this PR applied: chat.postMessage succeeds because sendMessageSlack falls through to the explicit opts.token (the boot-resolved monitor context token) instead of strict-throwing on the snapshot-side SecretRef.

Independent reproduction footprints (full thread in #68237):

  • file-source secrets, Linux.
  • exec-source secrets via 1Password CLI wrapper, macOS 26.4.1 / arm64 / Node 25.9.0.
  • 2026.4.14 (323493f) works, 2026.4.15 (041266a) fails with the same config; A/B is deterministic across npm i -g openclaw@… flips on long-running gateways.

Risk / Mitigation

  • Risk: Loosening any strict-mode SecretRef check is a security-adjacent change because the underlying invariant is "do not silently read raw refs as strings." If resolveSlackAccount were globally relaxed, a future caller without a fallback could end up sending undefined as a Slack token and surface a confusing failure mode (or worse, leak the raw SecretRef object label into a log line). Letting the env-var fallback fire when a configured SecretRef fails to resolve would also be a credential-confusion vector (CWE-287) on misconfigured deployments where a stray SLACK_*_TOKEN env var exists.
  • Mitigation:
    • The relaxation is opt-in via a new parameter, defaulting to the current strict behavior. Every existing caller (provider boot, account listing, inspection) is unchanged. The only opt-in site in this PR is sendMessageSlack, which already has an explicit, boot-resolved override and an existing if (!token) throw "Slack bot token missing for account ..." guard at extensions/slack/src/send.ts (the existing resolveToken). If both explicit and fallback are absent, the user-facing error is the existing actionable "missing token" message, not the silent raw-SecretRef shape.
    • Per-credential env-fallback gating: when a credential is configured as SecretRef, the corresponding SLACK_*_TOKEN env var is blocked from silently impersonating it. Credentials that are unset entirely (legitimate env-only setups, e.g. extensions/slack/src/channel.ts:543 invoking sendMessageSlack without opts.token) continue to use the existing env fallback. This satisfies both the CWE-287 concern and preserves backwards-compatible env-only behavior.
    • Regression tests (extensions/slack/src/accounts.test.ts) pin all directions of the contract: strict mode still throws with the path-tagged error message; tolerant mode returns undefined credentials and botTokenSource: "none"; tolerant mode does not regress on already-resolved string credentials; env fallback is blocked per-credential when configured as SecretRef; env fallback still fires when no credential is configured.
    • The fix is contained to two production files plus their test (accounts.ts, send.ts, accounts.test.ts); no shared SDK surface, no public type, no schema, no zod model, no runtime snapshot, no IPC / wire protocol, no plugin contract is touched. CODEOWNERS-restricted security paths are not touched.
    • oxlint passes on all three production / test files (0 warnings, 0 errors), including typescript-eslint(no-redundant-type-constituents) which previously triggered on the pre-existing mergeSlackAccountConfig casts. Targeted vitest run in extensions/slack/src/accounts.test.ts is green (11 / 11, with 6 new cases). extensions/slack/src/send.blocks.test.ts and extensions/slack/src/send.upload.test.ts are green (22 / 22) so the existing send-path mocks (blocks.test-helpers.ts mocks resolveSlackAccount and is unaffected by the new optional parameter) continue to work. pnpm tsgo:extensions:test passes on the touched files (the test fixture's as unknown as OpenClawConfig cast is the same pattern as extensions/slack/src/action-runtime.test.ts and other slack tests that need to construct shapes the schema rejects).

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Channel: Slack
  • Channel auth / SecretRef handling
  • Reply / outbound dispatch
  • Tests (extends existing accounts.test.ts)

Linked Issue/PR

Fixes #68237

@openclaw-barnacle openclaw-barnacle Bot added channel: slack Channel integration: slack size: S maintainer Maintainer-authored PR labels Apr 19, 2026
@greptile-apps

greptile-apps Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression introduced in 2026.4.15 where all Slack outbound replies fail when botToken is configured via a SecretRef. The root cause is sendMessageSlack calling resolveSlackAccount, which eagerly invokes strict SecretRef resolvers that throw on unresolved refs — even though the send path already has a boot-resolved opts.token override.

The fix adds an opt-in tolerateUnresolvedSecrets flag to resolveSlackAccount that substitutes the gentle normalizeSecretInputString (returns undefined for non-string values) for the strict resolvers, and enables it only in sendMessageSlack. All other callers (provider boot, account listing) retain the strict behavior.

Confidence Score: 5/5

Safe to merge — targeted, opt-in change with regression tests covering both strict and tolerant modes.

The fix is well-scoped: the relaxation is strictly opt-in (default behavior unchanged for all other callers), the only opt-in site already has an explicit boot-resolved token override and an existing 'missing token' guard, and 3 new test cases pin both directions of the new contract. No shared SDK surface, schemas, or runtime snapshot machinery is touched.

No files require special attention.

Reviews (1): Last reviewed commit: "Slack/send: tolerate unresolved channel ..." | Re-trigger Greptile

@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from 2a14841 to 571cd35 Compare April 19, 2026 13:20

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

Copy link
Copy Markdown

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: 571cd35ec6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/slack/src/send.ts
@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from 571cd35 to c4055d3 Compare April 19, 2026 13:45
@zimeg

zimeg commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

👋 Hey @openperf! I was having trouble mirroring these errors at first. Editing this comment in review but the fix is working for me! FWIW I agree with:

The strict throw is correct at boot-time provider initialization (where there is no fallback)

And am using the configurations:

{
  "secrets": {
    "providers": {
      "filemain": {
        "source": "file",
        "path": "~/.openclaw/token.out",
        "mode": "singleValue"
      }
    }
  },
  "channels": {
    "slack": {
      "accounts": {
        "default": {
          "botToken": {
            "source": "file",
            "provider": "filemain",
            "id": "value"
          },
          "appToken": "xapp-1-example"
        }
      },
      "enabled": true,
      "mode": "socket"
    }
  }
}

Edit: I'm now curious too for why these secrets aren't resolved at runtime but this approach seems alright for an immediate fix! I'm concerned other callsites might require a similar patch but am unsure of this at the moment ⏳

The upstream condition under which the cfg snapshot ends up holding an unresolved SecretRef (rather than the resolved string the secrets-runtime activation produces at boot) is environment-dependent and not fully nailed down here

@openperf

Copy link
Copy Markdown
Member Author

Hey @zimeg — yeah, that's expected. With a clean boot the secrets runtime resolves
botToken into the snapshot at startup, so loadConfig() returns the resolved string
and the strict resolver never trips; the invalid_auth you saw is just Slack rejecting
whatever token actually got through.

I dug into the upstream snapshot-pollution path while writing this up. The trigger is
in finalizeRuntimeSnapshotWrite (src/config/runtime-snapshot.ts:115-130): when a
config write triggers a reload and the secrets-runtime refresh handler returns false
or has been cleared (i.e. activeSnapshot was previously cleared by an earlier failed
refresh — see clearOnRefreshFailure → clearActiveSecretsRuntimeState), the fallback
path re-pins loadFreshConfig() (raw cfg with SecretRef objects intact) as the
runtime snapshot
. That contradicts the design intent stated in the io.ts:1878
comment ("Keep the last-known-good runtime snapshot active until the specialized
refresh path succeeds, so concurrent readers do not observe unresolved SecretRefs
mid-refresh") — the fallback actively replaces with raw instead of preserving
last-known-good.

So a single transient secret-provider failure during a reload (1Password CLI slow exec,
brief file-unreadable atomic-write window, etc.) is enough to poison the snapshot for
all subsequent loadConfig() reads on a long-running gateway. That's why the reporters
hit it deterministically and a fresh-boot harness like yours doesn't.

To verify this fix, the new contract tests pin both directions of the opt-in directly:

pnpm test extensions/slack/src/accounts.test.ts # 11/11

For end-to-end, the easiest reliable handle I've found is to force a refresh failure
on a long-running gateway: boot with your config (works), briefly revoke read on the
secret source (chmod 0 ~/.openclaw/token.out), trigger any config-file write, restore
read, then send a Slack message — chat.postMessage should throw the unresolved-SecretRef
error. With this PR applied the reply succeeds.

@aisle-research-bot

aisle-research-bot Bot commented Apr 20, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Slack token env-var fallback can override configured SecretRef due to overly-strict isSecretRef check (tolerant mode)
1. 🟠 Slack token env-var fallback can override configured SecretRef due to overly-strict isSecretRef check (tolerant mode)
Property Value
Severity High
CWE CWE-287
Location extensions/slack/src/accounts.ts:88-110

Description

In resolveSlackAccount, tolerant mode attempts to prevent credential confusion by blocking SLACK_*_TOKEN env-var fallback when the configured token is an unresolved SecretRef.

However, the guard uses isSecretRef(merged.<token>), and isSecretRef is overly strict: it requires the object to have exactly three keys (source, provider, id). This means common/legacy/augmented SecretRef shapes (e.g., legacy refs missing provider, or refs with extra metadata fields) will not be recognized as SecretRefs, and env fallback will still be allowed.

Impact:

  • If an operator configures channels.slack.accounts.default.botToken (or app/user token) as a SecretRef but the runtime snapshot contains a variant shape not matching isSecretRef, then in tolerant mode normalizeSecretInputString(...) yields undefined and the resolver can silently accept process.env.SLACK_*_TOKEN instead.
  • This can cause Slack to authenticate using an unintended ambient env token (credential confusion / unintended account impersonation).

Vulnerable code:

const blockBotEnv = tolerantMode && isSecretRef(merged.botToken);
...
const envBot = baseAllowEnv && !blockBotEnv
  ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN)
  : undefined;
const configBot = tolerantMode
  ? normalizeSecretInputString(merged.botToken)
  : resolveSlackBotToken(...);
const botToken = configBot ?? envBot;

And isSecretRef rejects SecretRef-like objects unless they have exactly 3 keys:

if (Object.keys(value).length !== 3) return false;

Recommendation

Make env-fallback blocking depend on whether a secret input is configured, not on the narrow isSecretRef shape.

Options:

  1. Use coerceSecretRef (handles legacy shapes and env-template strings) and/or a looser shape check:
import { coerceSecretRef } from "openclaw/plugin-sdk/secret-input";

const botRef = tolerantMode ? coerceSecretRef(merged.botToken) : null;
const blockBotEnv = tolerantMode && botRef !== null;
  1. Alternatively, block env fallback whenever the config field is present but not a usable string in tolerant mode (and log/annotate the reason):
const configBot = tolerantMode ? normalizeSecretInputString(merged.botToken) : ...;
const blockBotEnv = tolerantMode && merged.botToken !== undefined && !configBot;

Also consider adding tests for:

  • legacy SecretRef without provider
  • SecretRef objects with extra metadata keys

This ensures SLACK_*_TOKEN env vars cannot silently override any configured secret reference, even when unresolved.


Analyzed PR: #68954 at commit f13c639

Last updated on: 2026-04-20T07:00:55Z

@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from 85fcf64 to 2742f2d Compare April 20, 2026 06:30
@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from 2742f2d to fa8877d Compare April 20, 2026 06:32

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

Copy link
Copy Markdown

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: fa8877de8d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +90 to +92
const blockBotEnv = tolerantMode && isSecretRef(merged.botToken);
const blockAppEnv = tolerantMode && isSecretRef(merged.appToken);
const blockUserEnv = tolerantMode && isSecretRef(merged.userToken);

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 Detect legacy SecretRef shapes before allowing env fallback

In tolerant mode, env fallback is blocked only when isSecretRef(merged.*Token) is true, but isSecretRef requires an exact {source, provider, id} object. The secret-resolution path (coerceSecretRef) still treats legacy {source, id} refs as configured unresolved secrets, so those inputs now bypass block*Env, produce undefined from normalizeSecretInputString, and then silently fall back to SLACK_*_TOKEN. That is a behavior change from the previous strict resolver (which threw) and reintroduces the credential-confusion case this change is trying to prevent for legacy config/snapshot shapes.

Useful? React with 👍 / 👎.

@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from fa8877d to a0dc171 Compare April 20, 2026 06:40
…stently

- Add CHANGELOG.md entry for openclaw#68954 (missing from original PR).
- Use tolerantMode local consistently in resolveSlackAccount (S1).
- Add inline comment to resolveSlackSendContext documenting the strict-mode
  invariant: params.cfg is the scoped channel-dispatch config (F2).
@openperf openperf force-pushed the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch from a0dc171 to f13c639 Compare April 20, 2026 06:57
@openperf openperf merged commit 2ad1709 into openclaw:main Apr 20, 2026
28 of 29 checks passed
@openperf

Copy link
Copy Markdown
Member Author

Merged via squash.

Thanks @openperf!

@openperf openperf deleted the fix/slack-send-tolerate-unresolved-secret-ref-68237 branch April 20, 2026 06:59

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

Copy link
Copy Markdown

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: f13c639c26

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +90 to +94
const blockBotEnv = tolerantMode && isSecretRef(merged.botToken);
const blockAppEnv = tolerantMode && isSecretRef(merged.appToken);
const blockUserEnv = tolerantMode && isSecretRef(merged.userToken);
const envBot =
baseAllowEnv && !blockBotEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : undefined;

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 Block env fallback for legacy SecretRef token shapes

In tolerant mode, env fallback is only blocked when isSecretRef(merged.*Token) is true, but isSecretRef requires an exact {source, provider, id} object. The secret-resolution path still treats legacy {source, id} objects as configured unresolved refs (via coerceSecretRef), so for default-account sends those inputs now become undefined through normalizeSecretInputString and then incorrectly fall through to SLACK_*_TOKEN. This is a behavior regression from the previous strict path (which threw) and can cause credential confusion when legacy snapshot/config shapes are present.

Useful? React with 👍 / 👎.

lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…th (openclaw#68954)

Merged via squash.

Prepared head SHA: f13c639
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack maintainer Maintainer-authored PR size: M

Projects

None yet

2 participants