openclaw - 💡(How to fix) Fix Bug: before_tool_call approval requests not deliverable to WebChat in offline deployments

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…

Error Message

→ tool blocked with error, user never sees prompt

Root Cause

requestPluginToolApproval in pi-tools.before-tool-call-Dgq1Vlty.js (line ~410) calls plugin.approval.request without passing source routing information:

// Current code — missing turnSourceChannel / turnSourceTo / turnSourceAccountId
const requestResult = await callGatewayTool("plugin.approval.request", ..., {
    pluginId, title, description, severity,
    allowedDecisions, toolName, toolCallId,
    agentId: params.ctx?.agentId,       // ✅ present
    sessionKey: params.ctx?.sessionKey,  // ✅ present
    timeoutMs, twoPhase: true
    // ❌ turnSourceChannel  — NOT passed
    // ❌ turnSourceTo       — NOT passed
    // ❌ turnSourceAccountId — NOT passed
});

However, the hook's toolContext already has channelId available (line 658):

...args.ctx?.channelId && { channelId: args.ctx.channelId }

This channelId is never forwarded into the approval request.

Code Example

// Current code — missing turnSourceChannel / turnSourceTo / turnSourceAccountId
const requestResult = await callGatewayTool("plugin.approval.request", ..., {
    pluginId, title, description, severity,
    allowedDecisions, toolName, toolCallId,
    agentId: params.ctx?.agentId,       // ✅ present
    sessionKey: params.ctx?.sessionKey,  // ✅ present
    timeoutMs, twoPhase: true
    // ❌ turnSourceChannel  — NOT passed
    // ❌ turnSourceTo       — NOT passed
    // ❌ turnSourceAccountId — NOT passed
});

---

...args.ctx?.channelId && { channelId: args.ctx.channelId }

---

before_tool_call → requireApproval
  → plugin.approval.request (missing turnSourceChannel)
Gateway: turnSourceChannel = null
      → falls back to session store lookup
        → sessionStore.origin.provider"feishu" (offline, unreachable)
          → approval delivered to unavailable channel → lost

---

Gateway: turnSourceChannel = null
    → session store lookup fails (no origin for subagent)
      → hasImmediateDecision = null
"Plugin approval unavailable (no approval route)"
          → tool blocked with error, user never sees prompt
RAW_BUFFERClick to expand / collapse

Environment

  • OpenClaw version: v2026.5.19
  • Offline deployment (no external messaging channels like Feishu/Telegram/Discord)
  • Only available channel: WebChat (webchat)

Symptoms

When a before_tool_call hook handler returns requireApproval to ask for user authorization, the approval request cannot be delivered to WebChat in offline environments. Instead:

  1. If the current session was initiated from a non-WebChat channel (e.g. feishu), the approval is routed to that unavailable channel and is never seen in WebChat
  2. If the session is a subagent session with no session-store origin binding, the approval may fail immediately with "no approval route" and the tool call is blocked without the user ever seeing a prompt

Root Cause

requestPluginToolApproval in pi-tools.before-tool-call-Dgq1Vlty.js (line ~410) calls plugin.approval.request without passing source routing information:

// Current code — missing turnSourceChannel / turnSourceTo / turnSourceAccountId
const requestResult = await callGatewayTool("plugin.approval.request", ..., {
    pluginId, title, description, severity,
    allowedDecisions, toolName, toolCallId,
    agentId: params.ctx?.agentId,       // ✅ present
    sessionKey: params.ctx?.sessionKey,  // ✅ present
    timeoutMs, twoPhase: true
    // ❌ turnSourceChannel  — NOT passed
    // ❌ turnSourceTo       — NOT passed
    // ❌ turnSourceAccountId — NOT passed
});

However, the hook's toolContext already has channelId available (line 658):

...args.ctx?.channelId && { channelId: args.ctx.channelId }

This channelId is never forwarded into the approval request.

Impact Chain

before_tool_call → requireApproval
  → plugin.approval.request (missing turnSourceChannel)
    → Gateway: turnSourceChannel = null
      → falls back to session store lookup
        → sessionStore.origin.provider → "feishu" (offline, unreachable)
          → approval delivered to unavailable channel → lost

For subagent sessions with no session store binding:

  → Gateway: turnSourceChannel = null
    → session store lookup fails (no origin for subagent)
      → hasImmediateDecision = null
        → "Plugin approval unavailable (no approval route)"
          → tool blocked with error, user never sees prompt

Why This Matters for Offline Deployments

In offline environments:

  • No Feishu, Discord, Telegram, etc. — only WebChat is available
  • Sessions may be initiated from various entry points (TUI, cron, internal triggers)
  • sessionKey alone is not sufficient for routing — the session store may point to a dead channel
  • WebChat is supported by the approval infrastructure (exec-approval-surface-CedT0phv.js explicitly handles "webchat" as kind: "enabled" with interactive buttons), but the routing never reaches it

Expected Behavior

  1. requestPluginToolApproval should pass turnSourceChannel (from params.ctx.channelId) to plugin.approval.request
  2. The Gateway's approval routing should consider WebChat as a valid delivery target when the source channel is unavailable
  3. If no other channel can accept the approval, it should fall back to WebChat rather than failing

Affected Code Locations

FileLine(s)Issue
pi-tools.before-tool-call-Dgq1Vlty.js410-420requestPluginToolApproval doesn't pass turnSourceChannel/turnSourceTo/turnSourceAccountId despite channelId being available in params.ctx
plugin-approval-DmlXSQBv.js30-45Gateway handler reads turnSourceChannel but receives null — falls through to unreliable session store heuristic
exec-approval-session-target-DKAamt4W.js~80-120resolveExecApprovalSessionTarget relies on turnSourceChannel for routing; null causes subagent sessions to fail completely

Impact

  • Offline users cannot use before_tool_call approval prompts via WebChat
  • Privacy-hardened deployments that disable external channels are forced to use unsafe configurations (ask: "off")
  • Subagent sessions are affected even in non-offline environments when session store binding is absent

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING