openclaw - 💡(How to fix) Fix Stale plugin approval waits should fail closed instead of failing agent turns

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…

plugin.approval.waitDecision can currently surface a stale or missing approval id as a generic runtime/tool failure. That is risky for any plugin or harness that uses deferred approvals, because approval state can legitimately disappear while an agent turn is still alive.

A stale approval should still fail closed from the tool's perspective, but it should be represented as a typed unavailable/declined approval outcome so the agent turn can recover cleanly and logs/events show the real cause.

This is broader than one plugin or channel. Any deployment with plugin approvals can hit this when approval lifecycle and caller lifecycle drift apart.

Error Message

When a missing approval id throws through the runtime, a safe denial can become a failed agent turn, interrupted tool handling, or misleading provider/harness error. That makes approval enforcement harder to trust and harder to debug. Classify known stale-approval waitDecision errors as an unavailable approval outcome, for example when the error message contains:

Root Cause

Deferred approval waits cross several reliability boundaries:

  • Gateway restart or hot reload while a turn is waiting for approval
  • stale browser/app clients holding old approval ids
  • approval records expiring before the caller resumes
  • long-running turns where the approval manager no longer has the request
  • native hook relay / app-server / before-tool-call paths handling the same approval lifecycle differently

When a missing approval id throws through the runtime, a safe denial can become a failed agent turn, interrupted tool handling, or misleading provider/harness error. That makes approval enforcement harder to trust and harder to debug.

Fix Action

Fix / Workaround

A local patch applying this behavior across the three surfaces above was tested with targeted regression coverage:

I can provide the local patch or open a PR if maintainers want that path, but filing this first because the reliability issue is general and should be handled consistently across approval surfaces.

Code Example

approval expired or not found
unknown or expired approval id
RAW_BUFFERClick to expand / collapse

Summary

plugin.approval.waitDecision can currently surface a stale or missing approval id as a generic runtime/tool failure. That is risky for any plugin or harness that uses deferred approvals, because approval state can legitimately disappear while an agent turn is still alive.

A stale approval should still fail closed from the tool's perspective, but it should be represented as a typed unavailable/declined approval outcome so the agent turn can recover cleanly and logs/events show the real cause.

This is broader than one plugin or channel. Any deployment with plugin approvals can hit this when approval lifecycle and caller lifecycle drift apart.

Why this matters

Deferred approval waits cross several reliability boundaries:

  • Gateway restart or hot reload while a turn is waiting for approval
  • stale browser/app clients holding old approval ids
  • approval records expiring before the caller resumes
  • long-running turns where the approval manager no longer has the request
  • native hook relay / app-server / before-tool-call paths handling the same approval lifecycle differently

When a missing approval id throws through the runtime, a safe denial can become a failed agent turn, interrupted tool handling, or misleading provider/harness error. That makes approval enforcement harder to trust and harder to debug.

Observed failure mode

A wait call can fail with messages like:

approval expired or not found
unknown or expired approval id

Those conditions indicate the approval is no longer available. The safe behavior is to deny/fail closed, but without turning the entire caller path into a generic failure.

Affected surfaces to audit

Local inspection found multiple waitDecision callers that need consistent stale-id handling:

  • Codex app-server approval roundtrip
  • agent before_tool_call require-approval handling
  • native hook relay approval waits

These should all classify stale/missing approval ids the same way.

Expected behavior

When plugin.approval.waitDecision receives a stale/missing approval id:

  • Tool execution fails closed / is declined.
  • The approval event is marked unavailable/stale/declined in a typed way.
  • The agent turn can continue or report the denial cleanly.
  • Unrelated waitDecision errors still throw normally.

Actual behavior

At least some waitDecision paths currently allow stale/missing approval-id errors to escape as generic failures. In live agent use this can present as interrupted turns, declined tool execution without clear cause, or approval bridge failures unrelated to the underlying model/provider.

This may be related to, or a more general form of, #86047.

Suggested fix direction

Classify known stale-approval waitDecision errors as an unavailable approval outcome, for example when the error message contains:

  • approval expired or not found
  • unknown or expired approval id

Then return the same fail-closed result the caller would use for an unavailable approval, while preserving current throw behavior for unrelated errors.

Validation evidence

A local patch applying this behavior across the three surfaces above was tested with targeted regression coverage:

  • node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts src/agents/agent-tools.before-tool-call.e2e.test.ts passed, 48/48.
  • ./node_modules/.bin/vitest run extensions/codex/src/app-server/approval-bridge.test.ts --config vitest.config.ts --pool forks --testTimeout 10000 passed, 41/41.
  • TMPDIR=\"$(mktemp -d)\" ./node_modules/.bin/vitest run src/agents/harness/native-hook-relay.approval-wait.test.ts --config vitest.config.ts --pool forks --testTimeout 10000 passed, 2/2.

I can provide the local patch or open a PR if maintainers want that path, but filing this first because the reliability issue is general and should be handled consistently across approval surfaces.

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

When plugin.approval.waitDecision receives a stale/missing approval id:

  • Tool execution fails closed / is declined.
  • The approval event is marked unavailable/stale/declined in a typed way.
  • The agent turn can continue or report the denial cleanly.
  • Unrelated waitDecision errors still throw normally.

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 - 💡(How to fix) Fix Stale plugin approval waits should fail closed instead of failing agent turns