ExitPlanMode: plain "Accept" tells the model "You can now start coding", authorising implementation the user declined

Status Open
Maintainer reply None cached
Activity 1 comment · opened Sep 4, 2026

Summary: In plan mode, the ExitPlanMode approval dialog offers two distinct choices —
"Accept" and "Accept and start implementing". Choosing the narrower option ("Accept")
delivers a tool result to the model that explicitly authorises implementation, using the same
language the broader option would. The model has no way to distinguish "plan approved, do not act"
from "plan approved, proceed", so it begins editing files immediately after the user chose the
option that means don't.

This is an authorization-scope defect: consent is granted for scope A and reported to the agent as
scope A+B. No adversary is required — the agent itself is the actor, and the result is state change
the user did not authorise.

Environment: Claude Code running inside the Claude Desktop app (Code tab), model
claude-opus-5, macOS / Darwin 25.6.0, Apple Silicon. Session permission mode: Manual.
Observed 2026-09-04. (Claude Code bundle version not surfaced in-session; the reporter can add it.)

---

1. What the model receives on plain "Accept"

Verbatim from the ExitPlanMode tool result after the user pressed "Accept" — not
"Accept and start implementing":

User has approved your plan. You can now start coding. Start with updating your todo list if applicable

Your plan has been saved to: ~/.claude/plans/<plan>.md
You can refer back to it if needed during implementation.

## Approved Plan:
<full plan text>

and, appended to the same result:

## Exited Plan Mode

You have exited plan mode. You can now make edits, run tools, and take actions.
The plan file is located at ~/.claude/plans/<plan>.md if you need to reference it.

A PostToolUse hook then added further implementation pressure:

PostToolUse:ExitPlanMode hook additional context: After implementing the plan, follow
<verification_workflow> to verify.

Verification steps from the plan:
1. <build command>
2. <unit tests>
...

So on the "don't implement" branch the model is told, three times over: you can now start coding,
you can now make edits, run tools, and take actions, and after implementing the plan, verify it.

2. Why this is a permission defect, not a prompt-interpretation problem

The two buttons are the specification. The existence of a separate
"Accept and start implementing" affordance defines "Accept" as *approve the plan without starting
implementation*. That is not an inference the model has to make from ambiguous wording — it is the
UI's own contract, and the tool result contradicts it.

Two further signals were also not reflected in the result:

  1. Session permission mode was Manual, i.e. the user wants to authorise actions individually.

The result text is identical regardless.

  1. The user's approval was for an analysis and approach. The plan itself explicitly left two

decisions open (which priority to start, and the release vehicle), so there was nothing
unambiguous to implement even on a permissive reading.

3. Impact

On approval of scope "save the plan", the agent proceeded to act. In this session the user
interrupted after two steps (a session-metadata call and one read-only file read), so no source
file was modified. That was luck and an attentive user, not a control.

Had it continued, the plan in question called for edits across six source files. Generally, an
agent that believes it has implementation authority in this state may:

  • modify or overwrite working-tree files;
  • create commits — which in many repositories run .git/hooks/* and trigger out-of-process work;
  • invoke any configured MCP server or CLI tool, including ones that write to systems outside the

machine;

  • consume the user's token budget without authorisation.

The severity should not be assessed from what happened to be reachable in one particular repo. The
defect is that the agent acted outside granted permission scope; the blast radius is a property of
whatever session it happens in, and is not the user's to discover after the fact.

4. Reproduction

  1. Launch Claude Code in the Claude Desktop app (Code tab). Set permission mode to Manual.
  2. Enter plan mode. Have the model research something and produce a plan, ending in a call to

ExitPlanMode.

  1. In the approval dialog, press "Accept" — not "Accept and start implementing".
  2. Inspect the ExitPlanMode tool result delivered to the model. It contains

You can now start coding and You can now make edits, run tools, and take actions.

  1. Observe the model begin implementing without further user input.

Expected at step 4: a result stating the plan was saved and that implementation is not
authorised.

5. Suggested fix

  1. Make the two buttons produce materially different tool results. On plain "Accept", replace

the authorisation language with an explicit negative — e.g. *"The user approved the plan and
saved it. The user has NOT authorised implementation. Do not edit files or run state-changing
tools; wait for explicit direction."* Silence is not enough here: the current text has to be
removed, because "You can now make edits" is an affirmative grant.

  1. Do not append the "Exited Plan Mode / you can now make edits" block on the non-implementing

branch. If plan mode must be exited for mechanical reasons, say so without conferring
permission.

  1. Suppress the PostToolUse verification-workflow nudge on that branch — it presumes

implementation has been authorised.

  1. Reflect the session permission mode in the result. In Manual, an approval of one artefact

should never read as blanket clearance.

  1. Consider a regression test asserting that the two branches' model-visible strings differ and

that the non-implementing branch contains no permission-granting phrase.

6. Note on classification

Filed as a bug rather than through a private security channel because the disclosure risk is nil —
there is no exploit primitive here for a third party, and the fix is a message-string and gating
change. It is nonetheless a security-relevant authorization defect rather than a UX polish
issue: it converts a scoped user consent into a broader one, which is the property that permission
gating exists to preserve. Please route accordingly if the security team would rather own it.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗