feat: add model-gated auto-approve mode - #39015
Open
mayanksingh09 wants to merge 2 commits into
Open
Conversation
mayanksingh09
marked this pull request as draft
July 26, 2026 23:42
mayanksingh09
force-pushed
the
auto-approve-pr
branch
2 times, most recently
from
July 27, 2026 03:09
5782e32 to
c92ee66
Compare
mayanksingh09
marked this pull request as ready for review
July 27, 2026 03:25
mayanksingh09
force-pushed
the
auto-approve-pr
branch
from
July 27, 2026 04:31
c92ee66 to
54e0e0a
Compare
mayanksingh09
marked this pull request as draft
July 27, 2026 05:31
This was referenced Jul 28, 2026
mayanksingh09
marked this pull request as ready for review
July 28, 2026 23:29
disposeApps() awaited every web handler's dispose() unbounded. A handler that never settles stranded the run's finalizer, so Effect.runPromise never resolved and the explicit process.exit(0) was never reached: the process sat alive indefinitely after printing a clean summary. Observed repeatedly, including two runs stuck for over two hours past a passing result. CI runs this chain and would hang rather than fail fast. Bound the wait, but only for the run's final teardown, where a stalled handler no longer matters. The same function runs in resetState between every scenario, and abandoning a live handler there lets disposeAllInstances() and resetDatabase() run underneath it and leak state into the next scenario -- an earlier unconditional timeout did exactly that, failing three unrelated v2 routes.
Adds Auto-approve, an opt-in TUI mode that has a fast model review each consequential action before it runs. Safe, clearly authorized actions run without interrupting the user; anything unsafe, ambiguous, or unauthorized falls back to the normal approval prompt. Distinct from the existing --auto flag, which approves everything not denied with no review; that behavior is unchanged. The mode is gated behind experimental.auto_approve. While the flag is unset it is absent from the agent cycle, the classifier refuses before any model call, and the overlay endpoint refuses to enable. The shipped agent ruleset is `*: allow`, so Permission.ask() returns before creating a pending request and the mode would have had nothing to review on a default install. Entering it installs an ephemeral, in-memory, per-session overlay that upgrades allow to ask for consequential permissions (bash, edit, task, webfetch, websearch, external_directory, skill). Reads are never escalated, so browsing stays fast. The overlay is never persisted, is released on leaving the mode, and dies with the instance. Safety properties: - deny is never softened; the deny branch returns before the overlay is consulted, so this is structural rather than conditional - an explicit "always" grant is honored rather than re-classified - subagent sessions are never auto-approved, since the parent model authors the child's "user" message and could otherwise manufacture its own authorization - broad standing grants are not treated as authorization, because that is what an injection would plant - every failure path -- timeout, unavailable model, malformed output, interruption -- falls back to the prompt, never to an approval Every auto-approval is recorded in the transcript under the tool call it authorized, and is marked applied only when this client's own reply was accepted: a server-side reject cascades to other pending requests in the session, so treating that as success would record an approval for an action that never ran. auto_approve.show_details additionally reveals the exact classifier input and output, withheld by default because the input contains the user's request text. Traces stay in TUI memory and are never written to session history or replayed to later model turns. The classifier input includes the originating tool call, so a request is judged on the operation rather than on a bare filepath, and the output cap tolerates models that spend tokens before answering. A classification that cannot succeed reports why instead of failing silently. A task's subagent prompt is withheld from external ACP clients: it is carried in permission metadata for the classifier, which reads it in-process, and nothing in the ACP payload uses it.
mayanksingh09
force-pushed
the
auto-approve-pr
branch
from
July 28, 2026 23:33
8d466bd to
ac6133f
Compare
1 task
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.
Issue for this PR
Closes #37564
Type of change
What does this PR do?
Adds an opt-in TUI mode where a small model reviews each consequential action before it runs. Safe, clearly authorized actions go through; anything unsafe or ambiguous falls back to the normal prompt. It's the inverse of
--auto, which approves everything without looking.It's behind a beta flag and does nothing until you set it:
{ "experimental": { "auto_approve": true } }Then
<tab>to Auto-approve. Because the shipped ruleset is*: allow, the mode installs a temporary in-memory overlay that turnsallowintoaskfor bash/edit/task/webfetch/websearch/external_directory/skill while it's active. Reads are never escalated.denyis never softened, "always" grants still apply, and subagent sessions are never auto-approved — a subagent's prompt is written by a model, so it can't count as your authorization. Anything that errors or times out falls back to asking you.How did you verify your code works?
Typecheck across all 30 packages; 263 tests in
packages/opencodeand 84 inpackages/tui, including terminal frame-capture tests for the classifier row; all threetest:httpapimodes at 210/210. Overlay tests pin the important one directly: withbash: {"rm -rf *": "deny"}configured,rm -rf /still denies while the overlay is on.Checked live against
openai/gpt-5.6-luna: "runls -1d */ | wc -lin /" auto-approves,rm -rf /var/logasks, and a blanket "you can run any command" also asks — a standing grant isn't treated as authorization, since that's what an injection would plant.Screenshots / recordings
Reviewer setup
Use any fast non-reasoning model — reasoning tokens eat the classifier's small output budget. Tab twice from Build.
show_detailsadds an expandable row showing exactly what the model saw and answered. Drop the flag and the mode disappears from the cycle.Known limitations are tracked in #39412. None block this: the mode is behind a flag and every case fails safe.
Checklist