Skip to content

fix(approval): distinguish policy vs non-persistable reason for missing allow-always (fixes #97069) (AI-assisted)#97077

Closed
liuhao1024 wants to merge 12 commits into
openclaw:mainfrom
liuhao1024:fix/approval-prompt-one-shot-reason
Closed

fix(approval): distinguish policy vs non-persistable reason for missing allow-always (fixes #97069) (AI-assisted)#97077
liuhao1024 wants to merge 12 commits into
openclaw:mainfrom
liuhao1024:fix/approval-prompt-one-shot-reason

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What Problem This Solves

When a command includes shell redirection (e.g., openclaw --version 2>&1), the approval prompt incorrectly explains the unavailability of "Allow Always" by blaming the approval policy:

"The effective approval policy requires approval every time, so Allow Always is unavailable."

This is misleading when the effective policy is ask=on-miss — the real reason is that the command cannot be persisted due to shell redirection or dynamic content. Users may misdiagnose their approval policy as ask=always and waste time troubleshooting.

Why This Change Was Made

The approval prompt text generation in exec-approval-reply.ts and exec-approval-forwarder.ts unconditionally used the policy-related message when allow-always was excluded from allowed decisions. However, allow-always can be excluded for two distinct reasons:

  1. Policy reason (ask=always): The effective policy requires approval every time
  2. Non-persistable reason (one-shot): The command includes shell redirection, dynamic content, or other elements that prevent it from being stored as a reusable approval pattern

The fix checks the ask parameter to distinguish these cases and shows an appropriate message:

  • ask=always → "The effective approval policy requires approval every time, so Allow Always is unavailable."
  • Otherwise → "Allow Always is unavailable because this command cannot be persisted (e.g., shell redirection or dynamic content)."

User Impact

Users will now see accurate information about why "Allow Always" is unavailable, reducing confusion and troubleshooting time. The fix is purely informational — no approval behavior changes.

Evidence

Real behavior proof

  • Behavior addressed: Approval prompt text incorrectly blames policy when allow-always is unavailable due to non-persistable command elements (shell redirection)
  • Environment tested: macOS, OpenClaw 2026.5.28, Node.js v22.22.3
  • Steps run after the patch: Called buildExecApprovalPendingReplyPayload and buildExecApprovalRequestMessage with both ask=always and ask=on-miss + allowedDecisions: ["allow-once", "deny"] to verify correct message text
  • Evidence after fix:
$ npx tsx -e "
import { buildExecApprovalPendingReplyPayload } from './src/infra/exec-approval-reply.ts';

const oneShotCase = buildExecApprovalPendingReplyPayload({
  approvalId: 'test-oneshot',
  approvalSlug: 'slug-oneshot',
  ask: 'on-miss',
  allowedDecisions: ['allow-once', 'deny'],
  command: 'openclaw --version 2>&1',
  host: 'gateway',
});
console.log(oneShotCase.text);
"

Approval required.

Run:

```txt
/approve slug-oneshot allow-once

Pending command:

openclaw --version 2>&1

Other options:

/approve slug-oneshot deny

Allow Always is unavailable because this command cannot be persisted (e.g., shell redirection or dynamic content).

Host: gateway
Full id: test-oneshot


- **Observed result after fix:** The prompt correctly says "this command cannot be persisted (e.g., shell redirection or dynamic content)" instead of "The effective approval policy requires approval every time"
- **What was not tested:** Live gateway approval flow with actual Telegram/Discord delivery (requires running gateway instance)

## How to Test

1. Run `node scripts/run-vitest.mjs run src/infra/exec-approval-reply.test.ts` — all 33 tests pass
2. Run `node scripts/run-vitest.mjs run src/infra/exec-approval-forwarder.test.ts` — all 23 tests pass
3. Run `node scripts/run-vitest.mjs run src/infra/exec-approvals-policy.test.ts` — all 67 tests pass

- [x] AI-assisted (Hermes Agent)

@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 27, 2026, 11:09 AM ET / 15:09 UTC.

Summary
Threads exec approval ask context through agent/tool-result, Telegram, plugin SDK reaction, Control UI, i18n, and test surfaces so missing Allow Always can show non-persistable copy instead of policy-only copy.

PR surface: Source +119, Tests +365. Total +484 across 58 files.

Reproducibility: yes. Current main is source-reproducible because ask=always and one-shot persistence both remove allow-always, while current prompt renderers show policy-only copy whenever that decision is missing.

Review metrics: 1 noteworthy metric.

  • Approval context metadata: 1 optional ask field added to approval-pending tool details. The new prompt copy depends on carrying ask context across renderers while preserving the existing missing/null fallback.

Stored data model
Persistent data-model change detected: serialized state: extensions/telegram/src/approval-handler.runtime.test.ts, serialized state: src/agents/bash-tools.exec-runtime.ts, serialized state: src/agents/embedded-agent-subscribe.handlers.tools.test.ts, serialized state: src/infra/exec-approval-forwarder.test.ts, serialized state: src/infra/exec-approval-forwarder.ts, serialized state: src/infra/exec-approval-reply.test.ts, and 25 more. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #97069
Summary: The canonical remaining problem is the misleading approval prompt in #97069; this PR and several sibling PRs are candidate fixes for that same root cause.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] Several open PRs target the same bug, so maintainers should choose one canonical implementation before landing and close or supersede the others afterward.
  • [P1] This PR focuses on visible prompt/rendering surfaces; the gateway exec.approval.resolve rejection for a manually submitted unavailable allow-always decision still uses policy-only copy, so maintainers should decide whether prompt-only scope is enough.

Maintainer options:

  1. Decide the mitigation before merge
    Land one canonical, tested fix for [Bug]: Approval prompt wrongly says effective policy requires approval every time for non-persistable redirected command #97069 that preserves missing/null ask fallback and covers the intended user-visible approval surfaces, then close superseded sibling PRs.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No automated repair remains; maintainer review should choose this or another open sibling as the canonical branch before merge.

Security
Cleared: The diff changes approval prompt copy plumbing, optional metadata, generated locale output, and tests; no dependency, workflow, secret, permission, install, or package-resolution concern was found.

Review details

Best possible solution:

Land one canonical, tested fix for #97069 that preserves missing/null ask fallback and covers the intended user-visible approval surfaces, then close superseded sibling PRs.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main is source-reproducible because ask=always and one-shot persistence both remove allow-always, while current prompt renderers show policy-only copy whenever that decision is missing.

Is this the best way to solve the issue?

Yes, if maintainers choose this branch as the canonical implementation. The ask-aware approach is a narrow renderer/data-plumbing fix; a typed unavailable-reason branch is broader and could cover the manual resolve-error path too, but it is more invasive for the reported prompt bug.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4010b81a77f4.

Label changes

Label justifications:

  • P2: This fixes a bounded approval prompt regression that causes repeated approval friction and policy misdiagnosis without blocking all exec usage.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The PR body includes after-fix terminal output, and inspected Mantis Telegram Desktop screenshots directly show the redirected-command prompt changing from policy-only to non-persistable copy.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output, and inspected Mantis Telegram Desktop screenshots directly show the redirected-command prompt changing from policy-only to non-persistable copy.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR body includes after-fix terminal output, and inspected Mantis Telegram Desktop screenshots directly show the redirected-command prompt changing from policy-only to non-persistable copy.
  • mantis: telegram-visible-proof: Mantis should capture Telegram visible proof. The PR changes visible Telegram approval prompt text, which is demonstrable in a short Telegram Desktop proof and has already been captured by Mantis.
Evidence reviewed

PR surface:

Source +119, Tests +365. Total +484 across 58 files.

View PR surface stats
Area Files Added Removed Net
Source 53 207 88 +119
Tests 5 365 0 +365
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 58 572 88 +484

What I checked:

  • Repository policy read: Root AGENTS.md plus scoped guidance for agents, plugin SDK, extensions, UI, and Telegram maintainer notes were read; the review applied the guidance to inspect sibling approval surfaces and require Telegram-visible proof. (AGENTS.md:1, 4010b81a77f4)
  • Current-main source reproduction: Current main removes allow-always for both ask === "always" and one-shot persistence, so downstream renderers cannot distinguish policy from non-persistable command state using only the allowed decision list. (src/infra/exec-approvals.ts:1819, 4010b81a77f4)
  • Current-main misleading renderer copy: Current main's reply and forwarder renderers show the policy-required explanation whenever allow-always is missing. (src/infra/exec-approval-reply.ts:378, 4010b81a77f4)
  • PR head reply fix: The PR head keeps null/omitted ask on the policy fallback and uses non-persistable copy only when a known non-always ask value is present. (src/infra/exec-approval-reply.ts:378, 97301db93ca8)
  • PR head cross-surface plumbing: The PR threads ask into agent tool-result details, Telegram payload construction, plugin SDK reaction prompts, and Control UI warning selection. (src/agents/bash-tools.exec-host-shared.ts:493, 97301db93ca8)
  • Focused regression coverage: The PR adds focused tests for omitted/null ask policy fallback and ask=on-miss non-persistable copy in reply, forwarder, and Telegram prompt paths. (src/infra/exec-approval-reply.test.ts:371, 97301db93ca8)

Likely related people:

  • gumadeiras: Authored commit ba735d0, which unified effective exec approval reporting/actions and added the generic unavailable Allow Always copy in the affected prompt surfaces. (role: introduced effective-policy approval prompt path; confidence: high; commits: ba735d015809; files: src/infra/exec-approval-reply.ts, src/infra/exec-approval-forwarder.ts, src/agents/bash-tools.exec-runtime.ts)
  • jesse-merhi: Authored commit c9707ab, which rebuilt exec authorization around the Tree-sitter planner and added one-shot/unavailable-decision behavior for unpersistable shell shapes. (role: one-shot approval behavior contributor; confidence: high; commits: c9707ab635b9; files: src/infra/exec-approvals.ts, src/agents/bash-tools.exec-host-gateway.ts, src/agents/bash-tools.exec-host-node.ts)
  • vincentkoc: Recent history around the Control UI locale sync pipeline and locale outputs points to this area for the generated i18n files touched by the PR. (role: adjacent UI i18n contributor; confidence: medium; commits: ee4fe4fb1e1d, 1840611fe67e, 7b7d645193d3; files: scripts/control-ui-i18n.ts, ui/src/i18n/locales/en.ts, ui/src/i18n/.i18n)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. labels Jun 26, 2026
@clawsweeper
clawsweeper Bot temporarily deployed to qa-live-shared June 26, 2026 18:18 Inactive
@openclaw-barnacle openclaw-barnacle Bot added the channel: telegram Channel integration: telegram label Jun 26, 2026
@openclaw-mantis

Copy link
Copy Markdown
Contributor

Mantis Telegram Desktop Proof

Summary: Mantis captured native Telegram Desktop before/after GIFs showing the approval prompt explanation change.

Main screenshot This PR screenshot
Baseline native Telegram Desktop screenshot Candidate native Telegram Desktop screenshot
Main This PR
Baseline native Telegram Desktop proof GIF Candidate native Telegram Desktop proof GIF

Motion-trimmed clips:

Raw QA files: https://artifacts.openclaw.ai/mantis/telegram-desktop/pr-97077/run-28256902601-1/index.json

@liuhao1024
liuhao1024 force-pushed the fix/approval-prompt-one-shot-reason branch from 69c4831 to a525aed Compare June 26, 2026 21:50
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M and removed size: S labels Jun 26, 2026
@clawsweeper clawsweeper Bot added the proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. label Jun 26, 2026
@liuhao1024
liuhao1024 force-pushed the fix/approval-prompt-one-shot-reason branch from a525aed to 5f6ee9c Compare June 26, 2026 22:48
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 26, 2026
@liuhao1024
liuhao1024 force-pushed the fix/approval-prompt-one-shot-reason branch 4 times, most recently from 2d9dd4b to 889bdff Compare June 27, 2026 00:55
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 27, 2026
@openclaw-barnacle openclaw-barnacle Bot added the app: web-ui App: web-ui label Jun 27, 2026
@clawsweeper clawsweeper Bot added the status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. label Jun 27, 2026
…ng allow-always

When allow-always is unavailable due to shell redirection or other
non-persistable command elements, the approval prompt now correctly
explains the real reason instead of misleadingly blaming the approval
policy.

Fixes openclaw#97069
Carry the  parameter through Telegram's shared approval payload
builders so the prompt correctly distinguishes ask=always policy
explanations from non-persistable one-shot commands.

- exec-approval-forwarding: pass request.request.ask
- approval-handler.runtime: pass view.ask for exec approvals
- Add regression tests for ask=always and ask=on-miss rendering
The shared buildExecApprovalPendingReplyPayload helper uses params.ask to
distinguish policy-required (ask=always) from non-persistable (one-shot)
Allow Always explanations. The embedded tool-result delivery path was
calling this helper without ask, causing ask=always approvals to render
the wrong non-persistable copy.

- Read ask from approval-pending tool details
- Pass ask through to buildExecApprovalPendingReplyPayload
- Add regression tests for ask=always and ask=on-miss on embedded path
…de exec hosts

Add ask?: ExecAsk to ExecToolDetails approval-pending variant and
buildExecApprovalPendingToolResult params. Pass hostAsk from both
gateway and node exec hosts so the embedded renderer reads real
production data instead of relying on manually injected test fields.
…olicy fallback for omitted ask

- Add ask param to buildApprovalPendingMessage so tool-result content
  distinguishes ask-always policy from non-persistable commands
- Pass ask from buildExecApprovalPendingToolResult to message builder
- Keep policy-required explanation as default when ask is omitted in
  exported reply helper, preserving backward compatibility for callers
  that pass filtered decisions without explicit ask
The forwarder condition was inverted: when ask is undefined (omitted),
it showed the non-persistable message instead of the policy-required
message. Align with exec-approval-reply.ts and buildApprovalPendingMessage
patterns where omitted ask defaults to the policy-required explanation.

Add regression tests for omitted ask in both forwarder and reply helpers.
…ng allow-always

Carry ask context through all approval prompt renderers so that
one-shot/non-persistable commands show the correct reason when Allow
Always is unavailable, instead of always showing the policy-required
explanation.

- Add ask param to buildApprovalPendingMessage, buildExecApprovalPendingToolResult,
  and ExecToolDetails
- Pass ask from gateway/node exec hosts and Telegram/forwarding surfaces
- Keep omitted ask as policy-safe fallback in exported reply helper
- Add regression tests for ask=always vs ask=on-miss across all surfaces

Fixes openclaw#97069
…ge builders

Add inline comments to both buildApprovalPendingMessage and
buildExecApprovalPendingReplyPayload explaining that when  is
omitted (e.g. plugin SDK callers passing filtered decisions without
the full exec context), the policy-required explanation is shown
rather than the non-persistable message.
…enderers

Update the remaining two prompt renderers that still showed policy-only
Allow Always warning when ask context was available:

- Control UI exec-approval.ts: check request.ask to select between
  non-persistable and policy-required i18n keys
- plugin-sdk approval-reaction-runtime.ts: pass view.ask through to
  buildManualInstructionSection for ask-aware message selection
- en.ts: add allowAlwaysUnavailableNonPersistable i18n key

Add focused tests covering ask=always (policy), ask=on-miss (non-persistable),
and omitted ask (policy fallback) scenarios.
…locales

- Use the same approvalDecisionAsk that filtered Allow Always decisions
  instead of hostAsk for the exec approval pending tool result ask field
- Run pnpm ui:i18n:sync to generate locale bundles for the new
  allowAlwaysUnavailableNonPersistable key
Pass view.ask only after discriminating approvalKind === 'exec' to
satisfy TypeScript's union narrowing for PendingApprovalView. Plugin
approval views do not carry ask, so the previous view.ask access was
a TS2339 error on exact-head CI.

Ref: Codex review finding on PR openclaw#97077
@liuhao1024
liuhao1024 force-pushed the fix/approval-prompt-one-shot-reason branch from fd7e4f4 to cee5dee Compare June 27, 2026 14:31
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 27, 2026
…checks

Normalize null and undefined in the ask-aware unavailable-copy branches
across reply, forwarder, and Control UI renderers so that explicit
ask: null uses the policy-required fallback instead of the non-persistable
message. Add focused null regression tests for reply and forwarder.

Addresses Codex review finding on PR openclaw#97077.
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 27, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Thanks @liuhao1024. Closing this duplicate in favor of #97145 for #97069. #97145 carries the same ask-based fix plus the current manual resolve-error path and stronger current-UI proof; preserving #97069 and #97145 as the canonical tracker and implementation.

@steipete steipete closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: telegram Channel integration: telegram mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P2 Normal backlog priority with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants