openclaw - ✅(Solved) Fix [Bug]: Exec approval-pending does not block agent run loop, causing approval storm [2 pull requests, 3 comments, 4 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#52850Fetched 2026-04-08 01:18:31
View on GitHub
Comments
3
Participants
4
Timeline
8
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×2closed ×1locked ×1

When gateway exec approvals are configured with ask: "always", an approval-pending tool result does not stop the agent run loop. The model immediately retries exec, each retry generates a fresh approval id, and the operator sees a cascade of duplicate approval requests for the same command.

Root Cause

  1. Configure gateway exec approvals with ask: "always" and security: "allowlist".
  2. Start a desktop/webchat session (e.g. ClawWork with turnSourceChannel: "webchat").
  3. Ask the agent to run a simple command such as uname -a.
  4. Observe the approval UI — multiple approval requests appear for the same command before the first one can be resolved.
  5. Approve the first request from the desktop approval UI.
  6. Observe that more approval requests keep appearing because the agent has already retried exec multiple times.

PR fix notes

PR #58860: fix(exec): resume agent session after approval completion

Description (problem / solution / changelog)

Summary

  • resume the original agent session when an approved async exec finishes and the followup still has a sessionKey
  • keep direct external sendMessage(...) delivery only as the no-session fallback
  • add regression coverage for external-route approval completions and continuation-oriented followup prompts

Change Type

  • Bug fix
  • New feature
  • Breaking change
  • Refactor
  • Docs
  • Test-only

Scope

This PR is intentionally narrow.

It only changes exec approval followup routing after an already-approved async exec completes.

It does not change:

  • exec approval request registration
  • exec allowlist / denylist policy evaluation
  • command execution behavior before approval
  • node pairing / control UI approval transport
  • unrelated skills / provider typing surfaces

Linked Issue/PR

  • Related #39648
  • Related #52850
  • Related #58797
  • Follow-up to merged PR #53702
  • This PR fixes a bug or regression

Root Cause / Regression History

Approved async execs could finish successfully, but channel-backed sessions preferred direct external followup delivery instead of resuming the original agent session.

In sendExecApprovalFollowup(), the presence of an externally deliverable origin route caused the code path to call sendMessage(...) directly, even when the original requester sessionKey still existed.

That produced a delivery-mirror style followup without waking the original session. In practice, the user could see an Exec finished ... message while the task stopped progressing until another user message arrived.

This patch makes the session continuation path authoritative whenever a sessionKey exists, while still preserving best-effort external delivery metadata on that resumed agent call.

Behavior Changes

Before:

  • approved async exec followups with an external route could bypass agent continuation
  • the user could receive an external completion message without the original task resuming
  • direct external delivery was preferred over session continuation whenever the route was deliverable

After:

  • approved async exec followups resume the original agent session whenever sessionKey exists
  • external route metadata is preserved on that continuation path via deliver, bestEffortDeliver, and route fields
  • direct external delivery remains only as the fallback when there is no session to resume

Regression Test Plan

Updated:

  • src/agents/bash-tools.exec-approval-followup.test.ts
  • src/agents/bash-tools.exec.approval-id.test.ts

Coverage:

  • followup success prompt tells the resumed agent to continue the task before replying
  • channel-backed followups with a live sessionKey use agent continuation instead of direct sendMessage(...)
  • no-session followups still fall back to direct external delivery
  • gateway exec approval flow with an external route resumes the original session and does not use direct outbound message delivery
  • delayed approval on a Discord session key does not trigger followup work before approval resolves, then auto-continues the same session after approval without requiring a second user turn

Repro + Verification

Observed in local runtime transcripts and gateway logs:

  • exec.approval.waitDecision resolved successfully
  • only an external Exec finished ... followup appeared
  • the original task did not continue until a new user turn arrived

With this patch:

  • focused regression tests pass for the exec-approval followup path
  • the new delayed-approval Discord continuation test verifies the real failure mode more directly:
    • the exec stays pending before approval resolves
    • no followup side effect occurs before approval
    • once approval resolves, the same Discord session is resumed automatically
    • no direct sendMessage(...) fallback is used while the session exists
  • manual validation on the installed runtime reproduced the original Discord approve flow and confirmed the task now continues after approval without needing an extra "好了嗎" message

Tests

Passed locally:

  • corepack pnpm exec vitest run src/agents/bash-tools.exec-approval-followup.test.ts src/agents/bash-tools.exec.approval-id.test.ts --reporter=verbose

Additional focused coverage added in this PR:

  • auto-continues the same Discord session after approval resolves without a second user turn

Did not pass end-to-end repo pre-commit:

  • pnpm check is currently blocked by unrelated upstream TypeScript issues in:
    • src/agents/skills.test-helpers.ts
    • src/agents/skills/local-loader.ts

Risks and Mitigations

Risk:

  • channel-backed approval completions now prefer session continuation whenever sessionKey exists, so any caller that relied on direct external-only followups in that state will now go through the resumed agent path

Mitigation:

  • the no-session fallback still uses direct external delivery
  • delivery metadata is preserved on the resumed path
  • regression coverage now locks the intended continuation behavior in place for both session-backed and no-session cases

AI assistance

AI-assisted: drafted and implemented with Codex, then locally reviewed and tested by me.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/bash-tools.exec-approval-followup.test.ts (modified, +39/-7)
  • src/agents/bash-tools.exec-approval-followup.ts (modified, +37/-45)
  • src/agents/bash-tools.exec.approval-id.test.ts (modified, +147/-0)

PR #58904: fix(agent): treat webchat exec approvals as native UI

Description (problem / solution / changelog)

Summary

  • treat webchat like Discord/Slack/Telegram for exec approval prompt guidance
  • stop telling agents to paste manual /approve commands when the runtime has native approval UI
  • add regression coverage for the webchat path

Why

This is a narrow residual after #58792 and #58860.

Those changes fixed the exec policy/config cluster and the async continuation path. One UI mismatch remained: webchat/control sessions still got the manual /approve system-prompt guidance even though approvals arrive through native UI.

Testing

  • pnpm test -- src/agents/system-prompt.test.ts
  • pnpm check

Related #52850 Related #58797

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/system-prompt.test.ts (modified, +18/-1)
  • src/agents/system-prompt.ts (modified, +7/-2)

Code Example

# Session transcript evidence (approval-pending → immediate retry loop)

# 1. Tool result returns approval-pending:
~/.openclaw/agents/main/sessions/03ad9be4-4bea-4a63-8e01-2b602c49f916.jsonl:63
  → tool result: approval-pending for id f3755559-3bca-4a49-b35c-1f1e07c3e580

# 2. Assistant immediately prints /approve as text and retries exec:
~/.openclaw/agents/main/sessions/03ad9be4-4bea-4a63-8e01-2b602c49f916.jsonl:64
  → assistant text: "/approve f3755559 allow-always"
  → assistant immediately issues new exec tool call for "uname -a"

# Same pattern repeats across many approval ids in the same transcript.

# Gateway evidence (approval resolution itself works fine):

# Request created:
/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10499
  → exec.approval.requested for f3755559-3bca-4a49-b35c-1f1e07c3e580

# Approval resolved successfully:
/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10624
  → gateway.req.sent method exec.approval.resolve

/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10625
  → exec.approval.resolved

/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10626
  → payload: { ok: true }
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Summary

When gateway exec approvals are configured with ask: "always", an approval-pending tool result does not stop the agent run loop. The model immediately retries exec, each retry generates a fresh approval id, and the operator sees a cascade of duplicate approval requests for the same command.

Steps to reproduce

  1. Configure gateway exec approvals with ask: "always" and security: "allowlist".
  2. Start a desktop/webchat session (e.g. ClawWork with turnSourceChannel: "webchat").
  3. Ask the agent to run a simple command such as uname -a.
  4. Observe the approval UI — multiple approval requests appear for the same command before the first one can be resolved.
  5. Approve the first request from the desktop approval UI.
  6. Observe that more approval requests keep appearing because the agent has already retried exec multiple times.

Expected behavior

  • After an approval-pending result, the agent should stop retrying the same exec call until an external approval decision is received.
  • One operator approval should unblock the specific pending request it targets, without creating a cascade of new approval ids for the same command.
  • In environments with a first-class approval UI (desktop/webchat), the model should not emit /approve ... as assistant text since that text has no effect.

Actual behavior

  • The original approval request is valid and can be resolved successfully via exec.approval.resolve.
  • The gateway emits exec.approval.resolved with ok: true for the resolved id.
  • Despite that, the agent has already retried exec multiple times before the operator can approve, creating additional pending approvals.
  • Assistant messages contain /approve <id> ... text, but those are only displayed to the user — they are not executed in desktop/webchat environments.
  • The result is an approval storm: the operator sees 5-10+ approval popups for one uname -a.

OpenClaw version

2026.3.22

Operating system

macOS 15.4

Install method

pnpm dev

Model

anthropic/claude-sonnet-4.5

Provider / routing chain

openclaw -> anthropic

Additional provider/model setup details

Default Anthropic direct routing, no proxies or model routers.

Logs, screenshots, and evidence

# Session transcript evidence (approval-pending → immediate retry loop)

# 1. Tool result returns approval-pending:
~/.openclaw/agents/main/sessions/03ad9be4-4bea-4a63-8e01-2b602c49f916.jsonl:63
  → tool result: approval-pending for id f3755559-3bca-4a49-b35c-1f1e07c3e580

# 2. Assistant immediately prints /approve as text and retries exec:
~/.openclaw/agents/main/sessions/03ad9be4-4bea-4a63-8e01-2b602c49f916.jsonl:64
  → assistant text: "/approve f3755559 allow-always"
  → assistant immediately issues new exec tool call for "uname -a"

# Same pattern repeats across many approval ids in the same transcript.

# Gateway evidence (approval resolution itself works fine):

# Request created:
/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10499
  → exec.approval.requested for f3755559-3bca-4a49-b35c-1f1e07c3e580

# Approval resolved successfully:
/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10624
  → gateway.req.sent method exec.approval.resolve

/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10625
  → exec.approval.resolved

/Users/x/Library/Logs/@clawwork/desktop/debug-2026-03-23.ndjson:10626
  → payload: { ok: true }

Impact and severity

  • Affected: All desktop/webchat users with ask: "always" exec approvals enabled
  • Severity: High (blocks normal exec workflow, confusing UX)
  • Frequency: 100% reproducible on every exec call requiring approval
  • Consequence: Operators face approval storm popups, wasted tokens from repeated exec retries, and misleading /approve ... assistant text that has no effect in first-class approval UI environments

Additional information

Root cause analysis points to two related problems:

  1. Run loop does not treat approval-pending as a wait state. After exec returns approval-pending, the agent immediately continues its tool-call loop, retrying the same command. Each retry creates a fresh approval id.

    • Suspected code: run-loop logic in src/agents/pi-embedded-subscribe.handlers.tools.ts and src/agents/pi-embedded-subscribe.handlers.messages.ts
  2. System prompt encourages model to self-emit /approve ... in all environments. The prompt at src/agents/system-prompt.ts instructs the model to print /approve <id> when it receives approval-pending. This is only useful in text-command channels (Telegram, Slack). In desktop/webchat with a first-class approval UI, it is misleading noise.

    • Suspected code: src/agents/system-prompt.ts (approval guidance section)

Suggested fix directions:

  • Primary: Make approval-pending a hard wait state in the run loop — intercept the result and block further tool execution until an exec.approval.resolved event arrives for that id.
  • Secondary: Condition /approve ... prompt guidance on source channel capabilities — keep it for text-command channels, replace with "wait for operator approval" for desktop/webchat.

Relevant source files to inspect:

  • src/agents/system-prompt.ts
  • src/agents/pi-embedded-subscribe.handlers.tools.ts
  • src/agents/pi-embedded-subscribe.handlers.messages.ts
  • src/agents/pi-embedded-subscribe.ts
  • src/agents/bash-tools.exec-runtime.ts
  • src/agents/bash-tools.exec-approval-request.ts

extent analysis

Fix Plan

To address the issue, we need to implement two main fixes:

  1. Make approval-pending a hard wait state: Modify the run loop to intercept approval-pending results and block further tool execution until an exec.approval.resolved event arrives for that id.
  2. Condition /approve ... prompt guidance: Update the system prompt to only include /approve <id> guidance for text-command channels and provide a "wait for operator approval" message for desktop/webchat environments.

Step-by-Step Solution

1. Modify Run Loop to Wait for Approval Resolution

In src/agents/pi-embedded-subscribe.handlers.tools.ts, update the handleToolResult function to check for approval-pending results and pause the run loop:

// src/agents/pi-embedded-subscribe.handlers.tools.ts
import { ToolResult } from './tool-result';

// ...

handleToolResult(result: ToolResult) {
  if (result.status === 'approval-pending') {
    // Pause the run loop and wait for exec.approval.resolved event
    this.pauseRunLoop();
    this.waitForApprovalResolution(result.approvalId);
  } else {
    // Continue with normal handling
  }
}

// New method to wait for approval resolution
waitForApprovalResolution(approvalId: string) {
  // Listen for exec.approval.resolved event
  this.eventListener.once(`exec.approval.resolved.${approvalId}`, () => {
    // Resume the run loop
    this.resumeRunLoop();
  });
}

2. Condition /approve ... Prompt Guidance

In src/agents/system-prompt.ts, update the getApprovalGuidance function to condition the prompt based on the source channel:

// src/agents/system-prompt.ts
import { SourceChannel } from './source-channel';

// ...

getApprovalGuidance(approvalId: string, sourceChannel: SourceChannel) {
  if (sourceChannel === 'webchat' || sourceChannel === 'desktop') {
    return 'Wait for operator approval';
  } else {
    return `/approve ${approvalId} allow-always`;
  }
}

Verification

To verify the fix, follow these steps:

  • Configure gateway exec approvals with ask: "always" and security: "allowlist".
  • Start a desktop/webchat session.
  • Ask the agent to run a simple command that requires approval (e.g., uname -a).
  • Observe the approval UI and verify that only one approval request appears.
  • Approve the request and verify that the agent resumes execution without creating additional approval requests.
  • Repeat the test for text-command channels (e.g., Telegram, Slack) and verify that the /approve <id> prompt is displayed correctly.

Extra Tips

  • Make sure to test the fix thoroughly in

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 an approval-pending result, the agent should stop retrying the same exec call until an external approval decision is received.
  • One operator approval should unblock the specific pending request it targets, without creating a cascade of new approval ids for the same command.
  • In environments with a first-class approval UI (desktop/webchat), the model should not emit /approve ... as assistant text since that text has no effect.

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]: Exec approval-pending does not block agent run loop, causing approval storm [2 pull requests, 3 comments, 4 participants]