From 56de93062801e7a0deac283f283d7ea235256ee5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Apr 2026 02:39:35 +0100 Subject: [PATCH] fix: honor codex approval decisions (#71338) (thanks @Lucenx9) --- CHANGELOG.md | 1 + .../src/app-server/approval-bridge.test.ts | 37 +++++++++++++++++++ .../codex/src/app-server/approval-bridge.ts | 26 +++++++++++-- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2f28fe331..a97aae6cea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Gateway/sessions: recover main-agent turns interrupted by a gateway restart from stale transcript-lock evidence, avoiding stuck `status: "running"` sessions without broad post-boot transcript scans. Fixes #70555. Thanks @bitloi. +- Codex approvals: keep command approval responses within Codex app-server `availableDecisions`, including deny/cancel fallbacks for prompts that do not offer `decline`. (#71338) Thanks @Lucenx9. - Plugins/Google Meet: include live Chrome-node readiness in `googlemeet setup` and document the Parallels recovery checks, so stale node tokens or disconnected VM browsers are visible before an agent opens a meeting. Thanks @steipete. - Context engine: keep safeguard compaction checks active after context-engine windowing and for `ownsCompaction` engines, so large transcripts can compact before prompt submission instead of waiting for provider overflow. Fixes #71325. Thanks @steipete. - Approvals: compact structured home-directory paths to `~` across Codex permission prompts and exec approval metadata without repeating them as a separate high-risk warning, while preserving filesystem root and wildcard host warnings. Thanks @steipete. diff --git a/extensions/codex/src/app-server/approval-bridge.test.ts b/extensions/codex/src/app-server/approval-bridge.test.ts index 7494a5a59f5..1ef725f04bb 100644 --- a/extensions/codex/src/app-server/approval-bridge.test.ts +++ b/extensions/codex/src/app-server/approval-bridge.test.ts @@ -775,6 +775,43 @@ describe("Codex app-server approval bridge", () => { ).toEqual({ decision: "decline", }); + expect( + buildApprovalResponse("item/commandExecution/requestApproval", undefined, "approved-once"), + ).toEqual({ + decision: "accept", + }); + expect( + buildApprovalResponse("item/commandExecution/requestApproval", undefined, "approved-session"), + ).toEqual({ + decision: "acceptForSession", + }); + expect( + buildApprovalResponse( + "item/commandExecution/requestApproval", + { availableDecisions: ["cancel"] }, + "approved-once", + ), + ).toEqual({ + decision: "cancel", + }); + expect( + buildApprovalResponse( + "item/commandExecution/requestApproval", + { availableDecisions: ["accept", "cancel"] }, + "denied", + ), + ).toEqual({ + decision: "cancel", + }); + expect( + buildApprovalResponse( + "item/commandExecution/requestApproval", + { availableDecisions: ["decline"] }, + "cancelled", + ), + ).toEqual({ + decision: "decline", + }); expect(buildApprovalResponse("item/fileChange/requestApproval", undefined, "denied")).toEqual({ decision: "decline", }); diff --git a/extensions/codex/src/app-server/approval-bridge.ts b/extensions/codex/src/app-server/approval-bridge.ts index 55cf909566a..9168b9019d3 100644 --- a/extensions/codex/src/app-server/approval-bridge.ts +++ b/extensions/codex/src/app-server/approval-bridge.ts @@ -260,10 +260,10 @@ function commandApprovalDecision( outcome: AppServerApprovalOutcome, ): JsonValue { if (outcome === "cancelled") { - return "cancel"; + return commandRejectionDecision(requestParams, "cancel"); } if (outcome === "denied" || outcome === "unavailable") { - return "decline"; + return commandRejectionDecision(requestParams, "decline"); } if (outcome === "approved-session") { if (hasAvailableDecision(requestParams, "acceptForSession")) { @@ -274,7 +274,9 @@ function commandApprovalDecision( return amendmentDecision; } } - return hasAvailableDecision(requestParams, "accept") ? "accept" : "decline"; + return hasAvailableDecision(requestParams, "accept") + ? "accept" + : commandRejectionDecision(requestParams, "decline"); } function fileChangeApprovalDecision(outcome: AppServerApprovalOutcome): JsonValue { @@ -517,6 +519,24 @@ function findAvailableCommandAmendmentDecision( ); } +function commandRejectionDecision( + requestParams: JsonObject | undefined, + preferred: "decline" | "cancel", +): JsonValue { + const available = requestParams?.availableDecisions; + if (!Array.isArray(available)) { + return preferred; + } + if (available.includes(preferred)) { + return preferred; + } + const alternate = preferred === "decline" ? "cancel" : "decline"; + if (available.includes(alternate)) { + return alternate; + } + return preferred; +} + function approvalResolutionMessage(outcome: AppServerApprovalOutcome): string { if (outcome === "approved-session") { return "Codex app-server approval granted for the session.";