Conversation
Codex does not advertise `decline` for file-change approvals, nor for command approvals under the `untrusted` approval policy, so the only reject option there is `cancel`, which interrupts the turn. Clients that set `clientCapabilities._meta.continueOnReject: true` on `initialize` now also get Codex's `decline` decision, right before `cancel`, whenever the decision set has `cancel` but no `decline`, and in every file-change approval. Without the capability, permission requests are unchanged, so the adapter still does not invent choices by default.
karki011
marked this pull request as ready for review
September 27, 2026 15:15
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #556
Summary
Adds an opt-in client capability,
clientCapabilities._meta.continueOnReject. A client that sets it oninitializegets Codex'sdeclinedecision ("No, continue without …",reject_once) right beforecancelwhenever Codex's decision set offerscancelbut notdecline, and in every file-change approval. A rejected action is then skipped and the turn continues. Without the capability, permission requests are byte-for-byte unchanged.Motivation
Codex doesn't advertise
declinefor file changes, for escalation requests inread-only, or for any command underapprovalPolicy: "untrusted"(#555). There the onlyreject_onceoption iscancel, which interrupts the turn and endssession/promptwithstopReason: "cancelled", although the client never sentsession/cancel. The app-server acceptsdeclinein all of these cases; it just isn't offered.#405 made
availableDecisionsauthoritative ("the adapter does not invent replacement choices"), and #26 relies oncancelinterrupting the turn after a rejected file edit. Making the extra option opt-in keeps both as the default and lets clients that want "skip this, keep going" ask for it.Evidence from a raw ACP client (fresh session per action):
stopReasonuntrustedecho hi > shell.txtallow_once,accept_execpolicy_amendment,cancelcancelcancelleduntrustedecho hi > shell.txtallow_once,accept_execpolicy_amendment,cancelcancelcancelledread-onlyapply_patchallow_once,allow_for_session,cancelcancelcancelledread-onlyapply_patchallow_once,allow_for_session,decline,canceldeclineend_turnread-onlyecho hi > shell.txt(escalation)allow_once,accept_execpolicy_amendment,decline,canceldeclineend_turnask-always/ 0.157.0ask-alwaysecho hi > shell.txtallow_once,accept_execpolicy_amendment,decline,canceldeclineend_turnask-always/ 0.157.0ask-alwaysapply_patchallow_once,allow_for_session,decline,canceldeclineend_turnask-always/ 0.157.0ask-alwaysecho hi > shell.txtallow_once,accept_execpolicy_amendment,decline,cancelcancelcancelledAfter
decline, the model said the action was rejected and finished normally.cancelkeeps interrupting the turn.The change
src/permissions/capabilities.ts(new):clientSupportsContinueOnReject(clientCapabilities)readsclientCapabilities._meta.continueOnReject === true, in the same style as theterminal_output_deltacapability.src/permissions/options.ts:commandDecisionOptions(params, continueOnReject = false): with the flag,withDeclineBeforeCancelinsertsdeclineright beforecancelwhen the parsed decision set hascanceland nodecline. It is not applied to the legacy additional-permissions fallback, which keeps accept and cancel only. The existing ordering and completeness checks run unchanged afterwards.fileChangeDecisionOptions(continueOnReject = false): with the flag, addsNo, continue without making these edits(decline) beforecancel.src/permissions/CodexApprovalHandler.ts: takes the flag as an optional constructor argument (defaultfalse) and passes it to both helpers.src/CodexAcpServer.tspassesclientSupportsContinueOnReject(this.clientCapabilities).docs/permission-extension.md: a new "Continue-on-reject capability" section (marked experimental), with cross-references from "Command and network decisions" and "File changes". "Does not invent replacement choices" stays true for everyone who doesn't opt in.Tests
approval-events.test.ts, with the capability set oninitialize:declinebeforecancelwhen Codex's decision set lacks it, using the exactuntrustedshape (accept, exec-policy amendment,cancel); selecting it returns{decision: "decline"}. The full permission request is pinned indata/approval-command-continue-on-reject.json.cancelselection still returnscancel, and an ACPcancelledoutcome still returnscancel, notdecline.declinewhen Codex already advertises it; adds nothing to a decision set withoutcancel; adds nothing to the legacy additional-permissions fallback.end_turnonce Codex completes the turn, and the adapter never callsturn/interrupt.declinebeforecanceland maps it todecline(prompt ends withend_turn);canceland ACP cancellation still returncancel.Without the capability: a new test pins the native file-change options (
allow_once,allow_for_session,cancel), and every existing test, including "supports the native accept-plus-cancel decision set without inventing decline", passes unchanged.E2E (
RUN_E2E_TESTS=true,read-onlymode with the capability):createAuthenticatedFixturegains an optionalclientCapabilitiesMetaargument. New cases check that declining an in-workspace file edit, and declining an in-workspace write command, both end withend_turnand leave the file unwritten.Local results:
npm run typecheckCI=true npm test: 60 files passed, 789 tests passed, 34 skipped (the e2e suites)npm run buildnpm run bundle:all: not run locally (no bun installed); the change adds no dependencies or build inputsnpm run test:e2e: not run (needs an OpenAI API key); the manual probe above covers the same casesCompatibility
continueOnReject: true. Existing clients, including ones that answer with "the firstreject_onceoption", see exactly today's options.declinecomes beforecancel, so a client that answers with the firstreject_onceoption gets "continue without it". Clients that choose byoptionIdcan offer both.declinefor command and file-change approvals even when it doesn't advertise it. Both decisions are part of the generated v2 types (CommandExecutionApprovalDecision,FileChangeApprovalDecision) and were verified live on Codex 0.156.1, 0.157.0 and 0.157.1.declineitself, the capability becomes a no-op for those prompts (no duplicate is added).🤖 Generated with Claude Code