fix(exec-approval): distinguish non-persistable command from ask=always policy in approval prompt#98215
fix(exec-approval): distinguish non-persistable command from ask=always policy in approval prompt#98215ianalloway wants to merge 330 commits into
Conversation
|
Thanks for the context here. I did a careful shell check against current Current main already solves the reported approval-copy problem through the broader merged implementation from #97740, so this competing branch is no longer necessary. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. So I’m closing this as already implemented rather than keeping a duplicate issue open. Review detailsBest possible solution: Keep the merged reason-neutral wording across all approval surfaces; it fixes the false policy attribution without adding a parallel unavailable-reason field or retaining this obsolete branch. Do we have a high-confidence way to reproduce the issue? No on current main. The shared reply, generic forwarder, foreground agent, reaction, gateway validation, and Control UI paths now use truthful command-specific unavailable copy. Is this the best way to solve the issue? Yes. The merged neutral-copy solution fixes every known renderer with less state plumbing and compatibility surface than this PR’s optional flag. Security review: Security review cleared: No concrete security or supply-chain concern remains relevant because this branch should close rather than merge. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against bc239a851618; fix evidence: commit cbc84767bd7e, main fix timestamp 2026-07-11T10:09:51Z. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9dd6623c7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@jesse-merhi all CI is green — production wiring is in place for gateway, node, embedded, and Telegram paths. Ready for merge when you have a moment. |
* Update Claude CLI billing docs * Sync Claude live testing billing note
…#97289) * fix(shared): truncate assistant error text on UTF-16 boundary Use truncateUtf16Safe from the existing utf16-slice utility instead of String.prototype.slice(0, 600), which can split a surrogate pair and produce malformed Unicode when the error message contains characters outside the Basic Multilingual Plane (emoji, rare CJK, etc.). * test(shared): add regression test for UTF-16 safe error truncation Verify formatRawAssistantErrorForUi does not produce dangling surrogates when truncating a fallback raw error message with a non-BMP character (emoji) straddling the 600-code-unit boundary. * fix(agents): truncate assistant error text fallback on UTF-16 boundary Use truncateUtf16Safe instead of String.prototype.slice(0,600) in formatAssistantErrorText and its companion comparison in isRawAssistantErrorPassthrough, preventing dangling surrogates when non-BMP characters straddle the 600-code-unit truncation boundary.
truncateReplyBody used a raw value.slice(0, MAX_REPLY_BODY_LENGTH - 3) to shorten long reply bodies. When the cut index fell between the two UTF-16 code units of a surrogate pair (e.g. an emoji), the slice left a lone high surrogate before the ellipsis, which renders as a broken glyph in the reply context shown to the agent. Replace the raw slice with sliceUtf16Safe from the plugin SDK so the truncation never cuts inside a surrogate pair. A normal (non-astral) body is unaffected. Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
sanitizeForConsole filtered control characters code-point-aware but then truncated with sanitized.slice(0, maxChars), which cuts on UTF-16 code units. When the cap landed between the two code units of an astral character (emoji, CJK extension, etc.) the output ended in a lone high surrogate before the ellipsis. Cap on code points instead. Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* fix(gateway): surface idempotency key in history metadata * test(gateway): avoid unsafe optional chain * fix(gateway): preserve oversized transcript idempotency keys * fix(gateway): preserve oversized idempotency keys * fix(gateway): reuse transcript json helpers --------- Co-authored-by: 吴杨帆 <[email protected]>
* ci(docker): publish releases to Docker Hub * ci(docker): clarify beta image tags
Dependency GuardThis PR changes dependency-related files. Maintainers should confirm these changes are intentional. Changed files:
Maintainer follow-up:
|
Dependency graph changes are blockedOpenClaw does not accept dependency graph changes through PRs unless a repository admin or security explicitly authorizes the current head SHA. Dependency updates are generated internally by maintainers so external PRs cannot change the resolved graph. Detected dependency graph changes:
Auto-scrub was not attempted because this PR changes package manifest dependency graph fields:
Dependency graph changes must be reviewed by security or handled by maintainers internally. Please remove lockfile changes manually if they are not needed. To remove lockfile changes, restore them from the target branch: git fetch origin
git checkout 'origin/main' -- 'pnpm-lock.yaml'
git commit -m 'chore: remove dependency lockfile change'
git pushIf this PR intentionally needs a dependency graph change, ask a repository admin or member of The action will approve the current head SHA ( |
|
Thanks for the careful analysis and focused tests here. Current This branch also includes a large amount of unrelated history, including an iOS change already present on |
What Problem This Solves
When `allowAlwaysPersistence.kind === "one-shot"` (for example, a command that uses shell redirection), `allow-always` is correctly excluded from the allowed decisions. The approval prompt still showed the same explanation used for an `ask=always` policy:
That is misleading because the command can be approved; it just cannot be saved as a reusable approval pattern.
Closes #97069
What Changed
exec-approval-reply.ts— added optionalnonPersistableCommandfield toExecApprovalPendingReplyParams. When true,buildExecApprovalPendingReplyPayloademits a distinct message explaining the command cannot be saved as a reusable pattern.bash-tools.exec-runtime.ts— same branch added tobuildApprovalPendingMessage(foreground/CLI approval message path).bash-tools.exec-types.ts— addednonPersistableCommand?: booleanto theapproval-pendingvariant ofExecToolDetails, so the flag survives the gateway→tool-result→embedded-handler hop.bash-tools.exec-host-shared.ts—buildExecApprovalPendingToolResultaccepts and passesnonPersistableCommandthrough to both the message builder and the details output.bash-tools.exec-host-gateway.ts— derives and passesnonPersistableCommand: effectiveAllowAlwaysPersistence.kind === "one-shot" && hostAsk !== "always"to the tool-result builder.bash-tools.exec-host-node.ts— same derivation usingallowAlwaysPersistenceandapprovalDecisionAsk.embedded-agent-subscribe.handlers.tools.ts—readExecApprovalPendingDetailsreturn type extended withnonPersistableCommand?: boolean; value read from details and forwarded tobuildExecApprovalPendingReplyPayload.exec-approval-forwarding.ts(Telegram) — derivesnonPersistableCommandfromallowedDecisionsandrequest.request.askand passes it through.Evidence
188/188 tests pass across 5 test files after the change, including two tests that directly assert the distinct copy for non-persistable vs. ask=always:
Specific assertions in
exec-approval-reply.test.ts(34/34):shows non-persistable message when allow-always is excluded by command type— asserts output contains"cannot be saved as a reusable pattern", not"policy requires approval every time"shows policy message when allow-always is excluded by ask=always policy— asserts output contains"policy requires approval every time", not"cannot be saved as a reusable pattern"Gateway test confirms wiring at the
buildExecApprovalPendingToolResultcall site:src/agents/bash-tools.exec-host-gateway.test.ts106/106 passing.Production Paths Now Wired
buildApprovalPendingMessage)ExecToolDetails)