Skip to content

[Bug]: WhatsApp text /approve cannot resolve exec approvals, while Telegram resolves the same ID #84456

@balaji1968-kingler

Description

@balaji1968-kingler

Bug type

Approval routing / channel command handling bug

Summary

In OpenClaw 2026.5.18, WhatsApp can trigger an exec approval and receive the approval prompt, but a manually typed WhatsApp /approve <id> allow-once fails with:

Failed to submit approval: unknown or expired approval id

The same pending approval ID can then be approved successfully from Telegram, and the original command resumes and completes. This suggests the approval object is still valid and globally pending, but the WhatsApp text-command approval path cannot see or resolve it.

Environment

  • OpenClaw: 2026.5.18
  • Runtime: Docker gateway container on Windows 11 / WSL2 host
  • Agent: main
  • Channels involved: WhatsApp direct chat and Telegram
  • Approval config: approvals.exec.mode = "both"
  • Codex plugin disabled

Reproduction

  1. From WhatsApp direct chat, send an agent command that requires exec approval, for example:
Run: python3 /home/node/.openclaw/workspace/projects/health-fitness/scripts/health_raw_cleanup.py

Reply: DONE when the script completes.
  1. WhatsApp receives an exec approval prompt. In this repro, WhatsApp delivered the same prompt twice.

  2. Approve from WhatsApp using the short approval slug:

/approve d8f84298 allow-once
  1. WhatsApp replies:
Failed to submit approval: unknown or expired approval id
  1. Repeat with the full UUID:
/approve d8f84298-34a5-4c29-b3e5-458157c103ec allow-once
  1. WhatsApp still fails with the same unknown or expired approval id error.

  2. Approve the same pending ID from Telegram.

  3. Telegram succeeds, and the exec command resumes and completes.

Expected behavior

A WhatsApp /approve <id> allow-once command from an authorized operator should resolve a pending exec approval created by a WhatsApp-originated turn, just as Telegram approval does.

Actual behavior

WhatsApp receives the approval prompt but cannot resolve the pending approval ID. Telegram can resolve the same ID afterward, proving the ID was not expired or consumed.

Sanitized log evidence

Relevant sequence from gateway logs, sanitized:

[whatsapp] Inbound message ... (run request)
[ws] res ✓ exec.approval.request
[whatsapp] Sending message ...
[whatsapp] Sending message ...              # duplicate approval prompt
[telegram] outbound send ok ...             # approval also delivered to Telegram

[whatsapp] Inbound message ... (/approve ...)
[ws] res ✗ exec.approval.resolve errorCode=INVALID_REQUEST errorMessage=unknown or expired approval id
[ws] res ✗ plugin.approval.resolve errorCode=INVALID_REQUEST errorMessage=unknown or expired approval id

[ws] res ✓ exec.approval.waitDecision ...
[telegram] outbound send ok ...
[ws] res ✓ exec.approval.resolve ...         # Telegram resolves same approval successfully
[agent/embedded] strict-agentic execution contract active: runId=exec-approval-followup:<same-approval-id>:...
DONE

No unauthorized or Ignoring /approve from unauthorized sender message was observed in this repro.

Additional observations

The forwarded WhatsApp approval prompt renders a broken generic instruction line:

Reply with: /approve  allow-once|allow-always|deny

The approval ID is shown elsewhere in the prompt, but the Reply with: line omits it. This looks like a separate rendering bug and may confuse manual approval, but it does not explain this repro because both short ID and full UUID were manually typed and still failed.

The duplicate WhatsApp prompt is also likely a separate delivery/dedup issue with mode = "both", where the originating WhatsApp session route and an explicit WhatsApp target are both delivered because their route tuples are not byte-identical.

Source-inspection hypothesis

Inspection of the source suggests the failure may be a visibility/client-path mismatch rather than parsing or authorization.

The generic text /approve path appears to be:

  • src/auto-reply/reply/commands-approve.ts
  • parseApproveCommand(...) parses /approve d8f84298 allow-once
  • handleApproveCommand(...) calls exec.approval.resolve through plain callGateway
  • the ID passed is parsed.id

By contrast, Telegram inline approval appears to use an operator approval-runtime client path:

  • extensions/telegram/src/exec-approval-resolver.ts
  • resolveApprovalOverGateway(...)
  • uses approvalRuntimeToken / approval-runtime client visibility

Gateway approval visibility appears to check admin/operator approval-runtime visibility first, then falls back to record-specific device/connection/client identity. A generic WhatsApp text-command callGateway connection may pass sender authorization but still be unable to see a pending approval bound to another device/connection/client identity, causing the resolver to return unknown or expired approval id.

This would explain why:

  • WhatsApp sender authorization does not show an auth error.
  • WhatsApp resolver returns unknown or expired approval id.
  • Telegram can resolve the same approval afterward.

Suggested fix direction

After the existing sender authorization checks pass in handleApproveCommand, generic text /approve resolution may need to use the same approval-runtime resolver path as Telegram inline approval, for example resolveApprovalOverGateway(...), instead of plain callGateway.

The important security constraint is that the approval-runtime resolver should only be used after the existing human/channel authorization checks have passed. The proposed change should be a transport/visibility fix, not an auth bypass.

Impact

WhatsApp can be used to trigger commands and receive approval prompts, but manual WhatsApp approval is not reliable. Operators must use Telegram as a workaround for approval resolution.

Not included

No phone numbers, chat IDs, group IDs, raw session JSONL, tokens, hostnames, or private runtime logs are included in this report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions