Skip to content

Rejecting a command or file edit can only end the turn when Codex doesn't offer decline #556

Open

Description

@karki011

Summary

A client that rejects one action often wants the agent to carry on without it ("skip this, keep going"), which is what Codex's own decline decision does. codex-acp only offers that option when Codex puts decline in availableDecisions. In several common cases it doesn't:

  • File changes: fileChangeDecisionOptions() always returns allow once, allow for these files and cancel. docs/permission-extension.md notes that FileChangeApprovalDecision includes decline, but "the native Codex file-change prompt does not currently advertise it".
  • Escalation requests in read-only: when the model asks to run a write command outside the sandbox, Codex sends availableDecisions of ["accept", {acceptWithExecpolicyAmendment}, "cancel"].
  • Commands under approvalPolicy: "untrusted" (see No mode asks before every command and file edit (an untrusted-style preset) #555): the same three decisions, for every command.

In all of these, the only reject_once option is cancel. Selecting it interrupts the turn, and session/prompt returns stopReason: "cancelled" although the client never sent session/cancel.

I understand from #405 and docs/permission-extension.md that the adapter deliberately "does not invent replacement choices", and from #26 that interrupting after a rejected file edit is intended for clients where the user then types what to do differently. So the proposal below keeps that contract by default and makes the extra option opt-in.

Reproduction

A raw ACP client over stdio, a fresh git repo as cwd, fresh session per action, gpt-6-luna at low effort.

codex-acp / Codex Mode Client opted in Action Options offered Answer Written? stopReason
1.13.1 / 0.156.1 untrusted forced onto a mode – echo hi > shell.txt allow_once, accept_execpolicy_amendment, cancel cancel no cancelled
1.13.1 / 0.156.1 untrusted forced onto a mode – apply_patch allow_once, allow_for_session, cancel cancel no cancelled
1.13.2-preview.5 / 0.157.1 untrusted forced onto a mode – echo hi > shell.txt allow_once, accept_execpolicy_amendment, cancel cancel no cancelled
main + proposed change / 0.157.0 read-only no apply_patch allow_once, allow_for_session, cancel cancel no cancelled
main + proposed change / 0.157.0 read-only yes apply_patch allow_once, allow_for_session, decline, cancel decline no end_turn
main + proposed change / 0.157.0 read-only yes echo hi > shell.txt (escalation) allow_once, accept_execpolicy_amendment, decline, cancel decline no end_turn
main + both proposed changes / 0.157.0 ask-always yes echo hi > shell.txt allow_once, accept_execpolicy_amendment, decline, cancel decline no end_turn
main + both proposed changes / 0.157.0 ask-always yes apply_patch allow_once, allow_for_session, decline, cancel decline no end_turn
main + both proposed changes / 0.157.0 ask-always yes echo hi > shell.txt allow_once, accept_execpolicy_amendment, decline, cancel cancel no cancelled

After decline, the model replied that the action was rejected and finished the turn normally, so the app-server accepts decline for both request types even when it doesn't advertise it. The same held on 1.13.1 / 0.156.1 and 1.13.2-preview.5 / 0.157.1, with decline added by a local adapter change. Nothing here changed between Codex 0.156.1 and 0.157.1.

Our client, Spark Outpost, found this while routing every Codex action through its own approval step: a user's "no" to one command ended the whole run.

Expected

A way to get a reject_once option that rejects the single action and lets the turn continue, as decline already does whenever Codex advertises it ("No, continue without running it").

A smaller, related point: ACP defines stopReason: "cancelled" as "The turn was cancelled by the client via session/cancel". Returning it after a permission rejection, with no session/cancel, makes it hard for clients to tell "the user stopped the run" from "the user rejected one action".

Proposal

An opt-in client capability. A client that sets clientCapabilities._meta.continueOnReject: true on initialize gets Codex's decline right before cancel:

  • in command and network approvals whose decision set has cancel but no decline (labelled No, continue without running it);
  • in every file-change approval (labelled No, continue without making these edits).

decline is never added twice, never to a decision set without cancel, and never to the legacy additional-permissions fallback. cancel, and an ACP cancelled outcome, still map to cancel. Without the capability nothing changes, so #405's contract and #26's interrupt flow stay the default. A draft PR implementing this, with tests, references this issue.

Alternatives we considered:

  • Always offer decline before cancel (no capability). Smallest change, but it changes behaviour for clients that answer with "the first reject_once option", and departs from "authoritative availableDecisions" for everyone.
  • Fix it in Codex: the app-server advertises decline in availableDecisions for untrusted and escalation requests, and adds availableDecisions to file-change requests. Then codex-acp needs no change for commands, because it already maps decline. We can file this in openai/codex if you'd rather go that way.

Would you accept the opt-in, or prefer one of the alternatives?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions