openclaw - ✅(Solved) Fix [Bug]: Slack exec approval buttons do not resolve approvals — commands time out despite user clicking Allow Once [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#71023Fetched 2026-04-25 06:08:31
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2mentioned ×2subscribed ×2closed ×1

Slack exec approval interactive buttons (Allow Once / Always Allow / Deny) are displayed correctly via Block Kit, and the gateway acknowledges the button click, but the approval decision is never forwarded to exec.approval.resolve. The approval times out after 30 minutes (exec.approval.waitDecision returns after 1800000ms) and the agent either outputs /approve <id> allow-once as plain text or reports a timeout. Control UI approvals for the same request work correctly.

Root Cause

In extensions/slack/src/monitor/events/interactions.block-actions.ts, handleSlackBlockAction() does not check for exec approval command text after handleSlackPluginBindingApproval() returns false. The Telegram plugin (extensions/telegram/src/bot-handlers.runtime.ts) has the correct pattern: it calls parseExecApprovalCommandText() on the callback data and, if matched, calls resolveApprovalOverGateway() to resolve the approval. The Slack plugin is missing this equivalent code path.

Fix Action

Fixed

PR fix notes

PR #71025: fix(slack): resolve exec approval decisions from interactive buttons

Description (problem / solution / changelog)

Summary

  • Problem: Slack exec approval buttons (Allow Once / Always Allow / Deny) are received by the gateway but the approval decision is never forwarded to exec.approval.resolve. Commands time out after 30 minutes despite the user clicking the button.
  • Why it matters: Exec approval via Slack is completely non-functional. Users must fall back to Control UI to approve commands.
  • What changed: Added exec approval resolution logic to handleSlackBlockAction in the Slack plugin, mirroring the existing Telegram implementation. After handleSlackPluginBindingApproval returns false for a reply action, the code now parses the interaction data with parseExecApprovalCommandText and, if matched, calls resolveApprovalOverGateway.
  • What did NOT change (scope boundary): No changes to the approval request delivery path, Control UI behavior, Telegram plugin, or gateway approval infrastructure.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #71023
  • Related #66916
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: handleSlackBlockAction() in interactions.block-actions.ts does not check for exec approval command text after handleSlackPluginBindingApproval() returns false. The button value (/approve <id> allow-once) falls through to enqueueSlackBlockActionEvent() and is delivered to the agent as plain text instead of being resolved over the gateway.
  • Missing detection / guardrail: No code path exists in the Slack plugin to parse exec approval commands from interactive button values and call resolveApprovalOverGateway().
  • Contributing context: The Telegram plugin has the correct implementation in bot-handlers.runtime.ts — it calls parseExecApprovalCommandText() on callback data and resolves via resolveApprovalOverGateway(). The Slack plugin was missing this equivalent code path.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/slack/src/monitor/events/interactions.block-actions.test.ts
  • Scenario the test should lock in: When a reply button interaction contains a valid exec approval command (/approve <id> allow-once), resolveApprovalOverGateway should be called with the correct approval ID and decision, and the event should not be enqueued as a system event.
  • Why this is the smallest reliable guardrail: A seam test can mock resolveApprovalOverGateway and verify it is called with correct params without requiring a live Slack connection.
  • Existing test that already covers this (if any): None — existing block-actions tests do not cover exec approval button interactions.
  • If no new test is added, why not: The fix mirrors the established Telegram pattern; a follow-up test PR is recommended.

User-visible / Behavior Changes

  • Clicking exec approval buttons (Allow Once / Always Allow / Deny) in Slack now resolves the approval and executes the command, matching Control UI behavior.
  • Previously, clicking these buttons had no effect and the approval timed out after 30 minutes.

Diagram (if applicable)

Before:
[Slack button click] -> handleSlackPluginBindingApproval (false)
                     -> enqueueSlackBlockActionEvent (plain text)
                     -> agent receives "/approve <id> allow-once" as text
                     -> incomplete turn / timeout

After:
[Slack button click] -> handleSlackPluginBindingApproval (false)
                     -> parseExecApprovalCommandText (match!)
                     -> resolveApprovalOverGateway
                     -> command executes

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No — resolveApprovalOverGateway uses the existing gateway WebSocket client
  • Command/tool execution surface changed? No — approval decisions were already reachable via Control UI; this enables the same path from Slack
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS 26.2 (arm64)
  • Runtime/container: Node 22.22.2
  • Model/provider: ollama/qwen3.6:27b
  • Integration/channel: Slack (Socket Mode)
  • Relevant config (redacted):
    {
      "channels": {
        "slack": {
          "enabled": true,
          "execApprovals": {
            "enabled": true,
            "approvers": ["U06TP4R5VRS"],
            "target": "both"
          }
        }
      },
      "tools": {
        "exec": {
          "security": "allowlist",
          "ask": "on-miss"
        }
      }
    }

Steps

  1. Send @OpenClaw ping -c 4 localhost in a Slack channel
  2. Wait for exec approval buttons to appear
  3. Click "Allow Once"

Expected

  • Command executes immediately after approval

Actual

  • Before fix: approval times out after 30 minutes, command never executes
  • After fix: command executes immediately

Evidence

  • Gateway log before fix: exec.approval.waitDecision 1799998ms (30-minute timeout)
  • Gateway log after fix: slack: exec approval resolved: <id> -> allow-once followed by command execution
  • Control UI approval for the same request works correctly (confirming gateway infrastructure is functional)

Human Verification (required)

  • Verified scenarios: Slack DM and channel exec approval with Allow Once button — command executes after approval
  • Edge cases checked: Unauthorized sender receives ephemeral "not authorized" message; plugin approval IDs (plugin:*) fall back correctly
  • What you did not verify: Always Allow persistence across sessions; Deny button behavior (expected to work symmetrically)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: updateSlackInteractionMessage may fail if the original message was deleted or the bot lacks permissions.
    • Mitigation: Wrapped in try/catch with best-effort semantics (same pattern used elsewhere in the Slack plugin).

Changed files

  • extensions/slack/src/monitor/events/interactions.block-actions.ts (modified, +57/-1)
  • extensions/slack/src/monitor/events/interactions.test.ts (modified, +10/-2)

PR #71028: fix(slack): resolve exec approval button clicks via gateway instead of system events

Description (problem / solution / changelog)

Summary

When a user clicks an exec approval button (Allow Once / Allow Always / Deny) in Slack, the approval decision is enqueued as a plain system event via enqueueSlackBlockActionEvent() instead of being resolved over the gateway WebSocket via exec.approval.resolve. This causes the approval to never reach the waiting agent, resulting in a 30-minute timeout.

Fixes #71023

Root Cause

handleSlackBlockAction() in interactions.block-actions.ts checks for plugin binding approvals via handleSlackPluginBindingApproval(), which correctly returns false for exec approvals (they aren't plugin bindings). The function then falls through to enqueueSlackBlockActionEvent(), which delivers the approval decision as a plain system event — the gateway never sees it.

The Telegram extension handles this correctly: after the plugin binding check, it calls parseExecApprovalCommandText() on the callback data and routes matches through resolveApprovalOverGateway().

Fix

New file: extensions/slack/src/exec-approval-resolver.ts

  • Mirrors Telegram's exec-approval-resolver.ts — calls resolveApprovalOverGateway() with Slack-specific context

Modified: extensions/slack/src/monitor/events/interactions.block-actions.ts

  • After the plugin binding check and before enqueueSlackBlockActionEvent(), parses the button value with parseExecApprovalCommandText()
  • If it's an exec approval, verifies sender authorization via existing isSlackExecApprovalApprover() / isSlackExecApprovalAuthorizedSender()
  • Calls resolveSlackExecApproval() to resolve via gateway WebSocket
  • Clears approval buttons from the Slack message after resolution
  • Blocks unauthorized senders with a verbose log entry and early return

New test: extensions/slack/src/exec-approval-resolver.test.ts

  • Verifies the resolver correctly forwards to resolveApprovalOverGateway() with proper params
  • Tests plugin prefix routing, allowPluginFallback passthrough, and Slack-specific display name

Flow (before → after)

Before:

  1. User clicks "Allow Once" → Slack fires block action
  2. handleSlackPluginBindingApproval()false (not a binding)
  3. Falls to enqueueSlackBlockActionEvent() → plain system event
  4. Agent never receives approval → 30 min timeout

After:

  1. User clicks "Allow Once" → Slack fires block action
  2. handleSlackPluginBindingApproval()false (not a binding)
  3. parseExecApprovalCommandText(pluginInteractionData) → matches approval command
  4. Authorization check → resolveSlackExecApproval()resolved via gateway WebSocket
  5. Approval buttons cleared from message

Changed files

  • extensions/slack/src/exec-approval-resolver.test.ts (added, +109/-0)
  • extensions/slack/src/exec-approval-resolver.ts (added, +26/-0)
  • extensions/slack/src/monitor/events/interactions.block-actions.ts (modified, +55/-1)

Code Example

[slack] interaction action=openclaw:reply_button:1:1 type=button user=U06TP4R5VRS channel=C0ARGBWUUM8
...
[ws] ⇄ res ✓ exec.approval.waitDecision 1799998ms conn=ff643286…5637 id=7b1c6ac1…b57d
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Slack exec approval interactive buttons (Allow Once / Always Allow / Deny) are displayed correctly via Block Kit, and the gateway acknowledges the button click, but the approval decision is never forwarded to exec.approval.resolve. The approval times out after 30 minutes (exec.approval.waitDecision returns after 1800000ms) and the agent either outputs /approve <id> allow-once as plain text or reports a timeout. Control UI approvals for the same request work correctly.

Steps to reproduce

  1. Configure OpenClaw 2026.4.22 with Slack channel, execApprovals.enabled: true, execApprovals.target: "both", and a local Ollama model (e.g. qwen3.6:27b).
  2. Send @OpenClaw ping -c 4 localhost in a Slack channel.
  3. OpenClaw posts an "Exec approval required" message with Allow Once / Always Allow / Deny buttons.
  4. Click "Allow Once" in Slack.
  5. The message updates to show "✅ Allow Once selected by @user".
  6. The command is never executed. After 30 minutes, exec.approval.waitDecision times out in the gateway log.
  7. The agent outputs the approval command as plain text: /approve <id> allow-once.

Expected behavior

After clicking "Allow Once", the approval should be resolved via exec.approval.resolve on the gateway WebSocket, and the command should execute immediately (same behavior as approving through Control UI).

Actual behavior

The Slack plugin receives the button interaction as openclaw:reply_button:1:1, passes it through handleSlackPluginBindingApproval() (which returns false since it's not a plugin binding approval), and then falls through to enqueueSlackBlockActionEvent(). The approval decision text (/approve <id> allow-once) is delivered to the agent as a plain system event instead of being resolved over the gateway.

Gateway log shows:

[slack] interaction action=openclaw:reply_button:1:1 type=button user=U06TP4R5VRS channel=C0ARGBWUUM8
...
[ws] ⇄ res ✓ exec.approval.waitDecision 1799998ms conn=ff643286…5637 id=7b1c6ac1…b57d

No exec.approval.resolve call is ever made from the Slack plugin.

Root cause

In extensions/slack/src/monitor/events/interactions.block-actions.ts, handleSlackBlockAction() does not check for exec approval command text after handleSlackPluginBindingApproval() returns false. The Telegram plugin (extensions/telegram/src/bot-handlers.runtime.ts) has the correct pattern: it calls parseExecApprovalCommandText() on the callback data and, if matched, calls resolveApprovalOverGateway() to resolve the approval. The Slack plugin is missing this equivalent code path.

Environment

  • OS: macOS 26.2 (arm64)
  • OpenClaw: 2026.4.22 (00bd2cf)
  • Node: 22.22.2
  • Model: ollama/qwen3.6:27b (local)
  • Channel: Slack (Socket Mode)

Additional context

  • Control UI approval works correctly for the same approval request
  • The issue is model-independent — the Slack plugin simply never calls exec.approval.resolve
  • Confirmed by comparing Slack and Telegram plugin source code: Telegram has parseExecApprovalCommandText + resolveApprovalOverGateway in its callback handler; Slack does not
  • Possibly related to #66916 (reported as local model issue, but root cause is the missing Slack approval resolution)

extent analysis

TL;DR

The Slack plugin is missing a code path to resolve exec approvals, which can be fixed by adding a call to parseExecApprovalCommandText and resolveApprovalOverGateway in the handleSlackBlockAction function.

Guidance

  • Review the interactions.block-actions.ts file in the Slack plugin and compare it with the equivalent code in the Telegram plugin to identify the missing code path.
  • Add a call to parseExecApprovalCommandText to check if the callback data matches an exec approval command, and if so, call resolveApprovalOverGateway to resolve the approval.
  • Verify that the exec.approval.resolve call is made from the Slack plugin after adding the missing code path.
  • Test the fix by reproducing the issue and checking if the approval is resolved correctly.

Example

// in interactions.block-actions.ts
if (!handleSlackPluginBindingApproval()) {
  const approvalCommandText = parseExecApprovalCommandText(callbackData);
  if (approvalCommandText) {
    resolveApprovalOverGateway(approvalCommandText);
  }
}

Notes

The fix is specific to the Slack plugin and does not affect other plugins. The issue is not related to the model or the environment, but rather a missing code path in the Slack plugin.

Recommendation

Apply the workaround by adding the missing code path to the handleSlackBlockAction function, as this will resolve the exec approvals correctly.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

After clicking "Allow Once", the approval should be resolved via exec.approval.resolve on the gateway WebSocket, and the command should execute immediately (same behavior as approving through Control UI).

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: Slack exec approval buttons do not resolve approvals — commands time out despite user clicking Allow Once [2 pull requests, 1 comments, 2 participants]