Book a call
RECIPE12mVERIFIED 2026-08-02 · CLAUDE CODE 2.1.220 · CODEX CLI 0.146.0 · ANTIGRAVITY CLI 1.1.9 · KIMI CODE CLI 0.31.1

Asking for a plan you can actually reject

Plan mode gates the writes. It doesn't make the plan worth rejecting — that's a shape you ask for. Six blocks that turn a step list into claims you can check.

The plan you can't argue with

Plan mode has a reputation as a slower way of saying yes and it's earned. You enable plan mode, get a nice numbered list back, have a look at it, don't see anything that stands out as obviously wrong so you approve it — and then find the decision you'd have objected to in the 40th file

The thing is, the plan wasn't bad, there was nothing in it that could've been bad, here's an example:

Let's say we have two tools and a plan mode for each of them (we had), both plans are for the same task in a two-files repository where fetch() retries 3 times with 0s delay. (This is not a tool comparison, the prompts were different too which is the entire thing) I isolated a variable that is the requested shape of the output.

There's plan mode 1:

  • Antigravity CLI

  • Prompt: task

  • Plan: plan without any code, in the prompt I've explicitly said so, here's an abridged excerpt (sub-bullets under items 3–5 are equivalent):

TEXT
#### 1. Audit Current Retry Loop
* Locate the target function or execution block handling retries in app.py.
* Identify existing retry triggers (e.g., specific HTTP status codes, connection
  errors, or custom exceptions).
* Note current parameters (e.g., fixed sleep duration, loop bounds).
#### 2. Define Configuration Constants
* Initial Delay (BASE_DELAY) ... Backoff Factor (BACKOFF_FACTOR) ...
  Maximum Delay (MAX_DELAY) ... Maximum Attempts (MAX_RETRIES) ...
  Jitter Flag (USE_JITTER) ...
#### 3. Refactor Delay Calculation Logic ...
#### 4. Refactor Exception & Flow Handling ...
#### 5. Verification & Testing Strategy ...

And plan mode 2:

  • Claude Code

  • Same task, but the prompt in this case was the 6-block-long shape this lesson is about, here's an abridged version as well:

TEXT
## 3. DECISIONS
- Sleep function: use stdlib time.sleep, rejected a custom/async sleep — no async
  code anywhere in this file, stdlib is simplest and needs no new dependency.
- Backoff base/formula: use base_delay * (2 ** attempt) with base_delay = 0.1s,
  rejected a configurable cfg["backoff_base"] — load_config() currently only
  returns retries/timeout, and nothing supplies a backoff value today.
  Could go either way ...
- No sleep after the final failed attempt: skip the sleep on the last iteration,
  rejected always-sleep-then-check ...
- Jitter: not adding it, rejected — nothing indicates concurrent callers where
  thundering-herd matters ...

## 4. ASSUMPTIONS
- UNVERIFIED: no test suite exists to check backoff behavior against — confirmed
  via find . -iname "*.py" ...
- UNVERIFIED: exact desired base delay (0.1s) — not specified ...

## 5. NOT DOING
- Jitter.
- Configurable backoff parameters via load_config()/env vars.
- A max-delay cap.
- Changing which exceptions are caught (still only TimeoutError).
- Logging/metrics around retries.

You can see eleven different reasons why to reject it which is roughly 4 seconds per bullet. Like: The delay is too low for this particular API, it should be configurable and there should be a cap.

For example, an earlier run of Claude Code in this very tool (same tool, same repo, same model, but a bare one-sentence-long task with no shape), behind the scenes set the maximum delay to 30s. In contrast to that, in this shaped run we've explicitly excluded the max-delay cap in NOT DOING. So it's like the only thing that differed was the shape, and even then we've made a genuine design decision which turned out to be different. And it's like only in this particular run we've been aware of that

In other words — plan mode doesn't make a plan rejectable, you do by requesting a different shape


What makes a line rejectable

Can you come up with an actual reason and say "no" to it in less than 10 seconds without looking at the code? If no (and "yes" is the only thing you can say), it's fluff — decoration pretending to be work

Here are some types of lines that pass this test:

  • A path to a file — you can easily verify if app.py really exists in the repository but you can't check if there's actually a retry module

  • A decision with the alternative it replaced — you can reject a choice, you can't reject "refactor the logic"

  • An assumption it couldn't prove — that's almost always a reason why plans go wrong

  • Something it didn't do — if you declare what's not part of the task upfront, excluding it costs nothing

  • A command showing it works — if it can't come up with such command, it doesn't know what done looks like

But here are some types of lines that don't pass:

  • Audit the current implementation — you postpone your actual opinion to "audit"

  • Refactor X — you postpone your actual opinion to "refactor"

  • Add appropriate error handling — you postpone your actual opinion to "add"

  • Add tests — you postpone your actual opinion to "add"

  • Ensure backwards compatibility — you postpone your actual opinion to "ensure"

These all are postponing the actual judgment


The six blocks

To make a plan easy to reject, ask for it in a fixed structure with some context around each block. Here are the six blocks:

  1. GOAL — a single-sentence description of the outcome you'll see once you're done, so you can be certain they're not working on sth else before you even begin reading

  2. FILES — paths to every file that will be created/modified/removed during the implementation, so you can diff the change with it at the end to see if there's any drift

  3. DECISIONS — every design decision, the alternative it replaces and a short reasoning around it, to transform a description into an actual argument

  4. ASSUMPTIONS — anything that's not certain just based on exploring the repository, marked as unverified with a command or file path that could be used to verify it

  5. NOT DOING — everything it's deliberately leaving alone

  6. DONE — a command that shows the implementation works

Blocks 4 and 5 are the ones people usually omit but are actually the most valuable. Forcing it to write assumptions down forces it to notice it's making them. And forcing it to create an exclusion list is basically locking the blast radius and communicating it to you during the revision which is still relatively cheap.

Also for block 3, it'd be good if we could tell it to mark everything that's really balanced so the reader sees a fork instead of just an obvious default when in fact it was a toss-up


The recipe

  • Activate plan mode before your initial prompt, enabling it during the run is a separate thing we cover in the repair-prompts lesson

  • Send both the task and the shape in one message, if you send the task only you'll get a narration of the diff

  • Read blocks 4, 5 and 3 — assumptions, exclusions and decisions, this is where the harm is, while the list is usually the least valuable part but catches the attention first

  • Refuse a specific line rather than the plan, providing the block number, line number and reason, pointing at a file is actionable, saying you don't like it is not

  • Ask for the entire revised plan instead of the diff, a half-approved plan is a plan nobody saw, ask even if your harness enforces it

  • Approve only once you can come up with an observation that could prove it wrong, if you can't think of anything that would mean DONE is incomplete so is the plan

  • Keep the approved plan somewhere the agent can access it, approving opens the floodgates so after that the plan becomes a piece of documentation and to be useful as a piece of documentation it needs to stay visible

A few examples of what doesn't work:

  • "Try again" — this is a re-roll, not a rejection, you're throwing away an entire planning pass in favour of another run which usually produces the same content worded differently

  • "Do it properly" — at least one of these harnesses interprets a vague "no" as an invitation to keep talking so it asks more questions and doesn't come back with a plan, tell it what you want instead of just being unhappy with sth


IN YOUR HARNESS

In Claude Code

The plan is the permission mode so there are four ways to get into it in docs:

  • Shift+Tab you can cycle between default → acceptEdits → plan during the session

  • /plan [description] you can type from the input line, task description is optional

  • --permission-mode plan flag when you launch claude

  • settings file entry {"permissions": {"defaultMode": "plan"}} setting the plan as a project's default mode

Even the model itself can enter plan mode using the EnterPlanMode tool.

The really important thing is that by pressing Ctrl+G you can load the proposed plan into your default editor and change it directly before Claude acts on it, this is the most raw form of saying no as it's about striking ideas, correcting wrong assumptions etc and sending the file back, no chat needed.

Approval — in the docs there are four ways of approving a plan:

  • "Yes, and use auto mode" — or "Yes, auto-accept edits" when auto mode isn't available to your account; either way the session leaves plan mode and starts editing

  • "Yes, manually approve edits" — approve, then review each edit individually

  • hand it to Ultraplan — you can send the plan to Ultraplan's app for a browser review

  • "No, keep planning" — stay in plan mode to still have the plan; this is what we're focusing on today as it's the most important thing and usually overlooked

The gotcha. It's worth keeping in mind that when you approve the plan (no matter how) you exit the plan mode, because as per docs the plan mode is a one-shot gate so once you approve it, you enter another permission mode the "approve" command refers to, and can edit. To get into planning again, use Shift+Tab or /plan prefix.

Second gotcha, for headless runs. Another thing worth mentioning is that if you run claude with -p flag it doesn't have an approver so it doesn't show ExitPlanMode. Run it and it opens by saying ExitPlanMode isn't available, then prints the plan directly as plain text:

TEXT
ExitPlanMode isn't available in this session, so here's the plan directly.

As a result — your repo is not changed, git status is clean, gate still locked. And that's what headless plan mode is about, it means you outline the plan and stop, not outline the plan and wait. So just make sure to save it.

In Codex CLI

/plan enters plan mode and can optionally carry a prompt with it. It's about the tui feature, so there's no plan option in codex --help or in codex exec --help, therefore no headless codex exec --plan equivalent. Once it's done, the plan is returned wrapped in <proposed_plan> tag and the client removes this tag from the stream and displays it as a separate cell.

Codex implements more of the six blocks on the tool side than anything else here; the 0.146.0 binary has embedded instructions for plan mode that split unknowns into two groups:

  • things which are knowable by investigating the repo or the system — do it before asking

  • preferences and trade-offs which aren't knowable — raise early, providing 2–4 bullet points with alternatives and recommending a default; if you don't get a response, go with the default and log it as an assumption in the plan.

TEXT
1. **Discoverable facts** (repo/system truth): explore first. ...
2. **Preferences/tradeoffs** (not discoverable): ask early. ...
   * Provide 2–4 mutually exclusive options + a recommended default.
   * If unanswered, proceed with the recommended option and record it as an
     assumption in the final plan.

Those two are blocks 3 and 4, natively supported by the tool.

The tool is also instructing to emit the <proposed_plan> tag only when the plan is ready — as in fully decided and leaves nothing for the implementer to decide; if there's a "TBD" in the plan, it means it's not ready by the tool's standards so we should stay in Plan mode instead of approving it.

The tool can only do reading, searching, static analysis or dry runs during planning; it can also run tests and builds even if they write to caches or build artifacts (target/, .cache/, snapshots) but not if they modify repo-tracked files; it's not possible to edit any file, run formatters that rewrite files or apply patches/migrations.

The tool is resilient, its mind can't be changed by the user's intent, tone and imperative phrasing; in the 0.146.0 binary the tui asks "Implement this plan?" after emitting it with three possible answers:

  • building it in the current session

  • building it in a new thread seeded with the plan (so it gets the plan as a statement of intent in a clean context which is the most effective way to make sure a planning conversation doesn't turn into an implementation one)

  • or rejecting and staying in Plan mode

If you reject the plan, it has a standard shape; if you don't communicate your concern well enough for the tool to understand what needs to be changed to address it, it'll ask about it and stay in the planning mode until you provide an answer, without emitting the <proposed_plan> tag. It's important to know that revisions are always full replacements, not incremental changes; therefore if you want to reject the plan, communicate the missing information or the decision you want instead of communicating your discontent.

update_plan is a todo/checklist tool and has nothing to do with Plan mode; people often confuse it but:

  • it doesn't enter nor leave Plan mode

  • running it while in Plan mode results in an error

  • therefore seeing a checklist in a regular session doesn't mean planning

In Cursor

Cursor ships two read-only modes — Ask and Plan — which are not the same thing, so it's good to highlight that based on what's said in the cursor-agent --help output:

TEXT
--mode <mode>   Start in the given execution mode. plan:
                read-only/planning (analyze, propose plans, no
                edits). ask: Q&A style for explanations and
                questions (read-only). (choices: "plan", "ask")
--plan          Start in plan mode (shorthand for --mode=plan).
                (default: false)
  • Ask — is asking questions (about code) which doesn't produce anything

  • Plan — is a planning phase with an approval-before-writes checkpoint which produces a document

Half the write-ups out there point at Ask as Cursor's planning gate, so just FYI — it's the wrong one. Starting plan mode looks like this:

BASH
cursor-agent --mode plan "add exponential backoff to the retry loop in app.py"
  • Shift+Tab cycles between Agent → Plan → Ask modes

  • /plan [prompt] changes the mode to plan and submits a prompt or with no argument displays the current plan

The thing is — looking at the last case you can see that plan is not a chat message which disappears, but persistent state that can be addressed; in fact it's a virtual file that you can read and edit, stored in your home directory (unless you change it), but also can be saved to workspace using Save to workspace option so it can be shared. Then in the editor, in the Agent panel, to accept it you need to click Build button. But don't assume it exists in the terminal, because even if you look at the CLI documentation you'll see how to enter plan mode and what's /plan about, but nothing about the Build button.

The gotcha. -p is not a gate. Its own help line says so:

TEXT
-p, --print   Print responses to console (for scripts or non-interactive use).
              Has access to all tools, including write and shell.
              (default: false)

Headless on its own is the opposite of read-only, so in a scripted run --mode plan is the part carrying the gate. We couldn't exercise the two together, so take that from the flag semantics rather than from us.

Second gotcha, which stops you before anything else does: cursor-agent refuses to start in a directory it hasn't been told to trust, plan mode included. Ours came back with

TEXT
⚠ Workspace Trust Required

  Cursor Agent can execute code and access files in this directory.
  Do you trust the contents of this directory?

    /private/tmp/a-plan-check

  To proceed, you can either:
    • Run 'agent' interactively to decide
    • Pass --trust, --yolo, or -f if you trust this directory

Trust the workspace once, interactively, before you script against it. Reaching for --yolo to get a read-only run moving is a trade nobody meant to make.

Cursor's own docs also give the clearest statement of the reject loop anywhere: whenever the output is not perfect you should avoid further prompts for correction, just revert the changes and focus on improving the plan, then run it again. That's what Cursor says — usually faster than correcting the agent and leads to cleaner output

In Antigravity CLI

So basically the binary has two execution modes, accept-edits and plan, and you pick one with a single --mode option. As of 1.1.9 the help lists both:

TEXT
--mode   Set the agent execution mode for this session (accept-edits, plan)

It's either plan, or accept-edits, you can choose which one you'd like to use during a session by setting it in the --mode option on the very beginning (plan) or by pressing Shift+Tab to cycle between them until you see plan in the status bar, in both cases it will be set for the entire session.

Just so you know, the documentation is pointed about the flag being --mode and not --permission-mode — agy uses --mode for execution overrides.

Also /plan is different than the other harnesses, with the rest of them when you type /name it changes the behaviour of the CLI for the rest of the session (to accept-edits for instance) while with /plan we just prepend it to a prompt so using --mode plan you tell the CLI to always prepend /plan before your prompt and if you want to do it for a single prompt you can type /plan yourself. So /plan in Claude Code or Codex switches a mode; here it just decorates a message.

The plan is an Artifact as well, a markdown implementation plan that gets added to the same queue of approves/rejects for code diffs and you can navigate it using keys:

  • ctrl+r - open the Artifact Picker Panel

  • y - approve

  • n - reject

  • p - preview

  • shift+a - approve all

The reason this is particularly useful is that rejection is recorded on the Artifact itself, so the plan carries what was decided about it and you can always come back to it later.

Just as a heads-up if you'd like to use --print option with plan remember that it consumes the prompt as its own argument and -p is an alias for --print so for example it might be tempting to run:

BASH
agy -p --mode plan "add exponential backoff to the retry loop in app.py"

but actually in such scenario --mode is being consumed by -p as it's the prompt of the --print option so the model sees --mode instead of your prompt and then tells you what --mode means, it doesn't show any error or warning. Instead you need to run:

BASH
agy --mode plan --print "add exponential backoff to the retry loop in app.py"

The other thing is that during our test we observed the plan was created without reading a single file, the first point of the plan was about finding a function and it included configuration constants and HTTP status codes not present in the repository at all. We can see we had this write protection set up correctly and git status was showing no changes but it's hard to reject a plan if it doesn't have any base. So just to make sure always ask the model to first open the file and then do a show of the lines that are gonna be modified.

In Kimi Code CLI

There are four ways you can enter the planning mode from the user's side:

  • using --plan flag

  • running /plan or /plan on/off command in the session

  • hitting Shift-Tab

  • setting default_plan_mode to true in config.toml

In addition to that, the agent can enter it using EnterPlanMode tool.

The structure of the keys in the config file shows that this is a separate layer of a boolean nature which sits above the permission mode (not one of its options). So it's not that there are for example three values of default_permission_mode (manual, yolo and auto) and we set it to true or false (plan mode); instead we can set both default_permission_mode and default_plan_mode to anything. In other words, plan mode in Kimi is not the same as it's in Claude Code, where plan is one of permission-mode values. That separation is what decides how the two settings combine, which comes up in the caveats below.

Of all the harnesses in this lesson, Kimi probably follows the assumptions behind the lesson the closest. When you run ExitPlanMode it loads the plan file and displays it to you. Then it waits for your input. The approval panel you get in this situation is a little different than in other cases — it gives you four fixed options:

  • Approve

  • Reject

  • Reject and Exit

  • Revise

As you can see, even though you can reject the plan, it's a pre-defined button so it's not like you need to come up with an appropriate phrase; "rejecting" is different from "rejecting and leaving planning", for example. Additionally, if you want to, the agent can provide you with between one and three of its alternative solutions (with labels and descriptions), which you can choose during the approval phase as well. That's block 3 of the six, rendered as a menu.

What's more, across all Kimi's approval panels, hitting Esc, Ctrl-C or Ctrl-D is treated as a rejection (the same key we press to abort if something goes wrong during a turn is used to say "no" too).

But there are a few caveats:

  • plan mode doesn't mean read-only in Kimi, and the docs outline it precisely:

    • Write and Edit are available only for editing the current plan file

    • TaskStop is not allowed at all

    • the rest of tools (including Bash) are still subject to the existing permission rules

    This means that if you have an allow rule for Bash in your config, you can use the same set of commands during planning as outside it — planning is exactly as loose as your rules are.

  • The only harness whose docs explicitly say that plan takes precedence over yolo is Kimi. In Kimi, using --yolo automatically approves calls of regular tools, and the docs say twice that it doesn't skip the plan-exit approval. Other harnesses are mostly quiet about this, but in Claude Code for example it's the opposite — if you start a session with bypassed permissions, then the plan mode's restrictions don't apply as well

  • auto mode approves the plan exits on your behalf, marking them as "Auto-approved" in the transcript

  • lastly, using --prompt and --plan is not possible; you can't even run the program with both of these flags, because non-interactive sessions use auto mode by default, so in such scenarios it's not possible to plan and postpone approval at the same time. You simply can't plan headlessly in Kimi

The limits

It's not a write lock

  • Claude Code:

    • Reads files

    • Runs shell commands to explore the repo

    • Writes a plan

    • Doesn't edit source code

  • Codex:

    • The boundary is repo-tracked state, so tests and builds that only write to caches or target/ are explicitly allowed

  • Kimi Code:

    • Write and Edit tools are restricted to the plan file

    • Two more tools are blocked outright while planning

    • Everything else including Bash is still bound by the normal permissions

Which means it's basically a tracked files lock, not a write lock in general

Approval ends it

According to the Claude Code docs, approving plan mode exits this mode and enters whatever the approve permission is called, which is editing for Claude Code so after that it's just a regular session with an attached document. So keep in mind you'll need to make sure the outcome is in line with the plan afterwards as that's your responsibility then

But there's one thing that makes it easy — the FILES block, diffing the change against it takes seconds and will catch any drift

It's not a specification

Plans are written before anyone touches the code, what matters during the implementation are the things we weren't aware of at the planning stage, and if you don't ask your agent to notify you about such things, they will go unrenegotiated

Headless has nobody to approve

If there's no one present to approve it, the approval step can't happen so where harnesses allow it, plan mode reduces to printing a plan and exiting. Which is useful in CI (you post it to the issue and a human approves later), but not an actual gate

Kimi Code goes a different way and doesn't even run in this setup

The gate isn't uniformly a gate

  • Claude Code:

    • If you enable bypass permissions, plan mode stops being a gate at all — it instructs the model to plan without editing but actually allows editing during planning

  • Kimi Code:

    • Plan-exit approval is still in place even with --yolo

So same feature name, opposite guarantees. Make sure which behaviour applies before relying on sth


Plan mode is a cheap point in time to say no, not no in itself, the no is usually the result of what you ask it for, and that prompt goes six blocks long

THE FILEplan-request.md
MARKDOWN
# Ask for a plan you can reject

Paste this under your task. Checked against the plan mode of Claude Code, Codex CLI,
Cursor, Antigravity CLI and Kimi Code CLI.

---

Before you change anything, give me the plan in exactly this shape:

1. GOAL — one sentence, as an observable end state.
2. FILES — every path you will create, edit or delete. Exact paths.
3. DECISIONS — each decision you made, the option you rejected, and why. One line
   each. Mark any that could genuinely go either way as COULD GO EITHER WAY.
4. ASSUMPTIONS — anything you could not confirm from the repo, marked UNVERIFIED,
   with the file or command I could check it against.
5. NOT DOING — the adjacent things you are deliberately leaving alone.
6. DONE — the check that proves it worked, as a command I can run.

Read the files before you write any of this. Do not write the implementation.

---

## Reading it

Read 4, then 5, then 3. Then the rest, if you still want to.
The step list is the least informative part of a plan and the first place your eye goes.

## Rejecting it

Name the block, name the line, give the reason. One line.

    Assumption 2 is wrong — the timeout is set per-caller, see client.py.
    Decision 3 went the wrong way. I want the configurable version.
    NOT DOING is empty. Tell me what you're leaving alone.
    DONE isn't a command. Give me something I can run.

Then: "give me the revised plan in full, not a diff of the old one."

These two don't work:

    Try again.                 -> a re-roll, not a rejection; costs a whole pass
    No, do it properly.        -> some harnesses read this as "keep talking",
                                  and you get discussion instead of a new plan

## Approving it

Only when you can answer: what would I observe if this turned out to be wrong?
If the answer isn't in DONE, the plan isn't finished.

## After approving

Approval opens the write gate. Diff the change against FILES — anything outside
that list is drift, and it costs you ten seconds to find it here instead of in review.

## Make it the default (optional)

Paste into your memory file (CLAUDE.md / AGENTS.md / the equivalent):

    When I ask for a plan, use six blocks: GOAL (observable end state), FILES
    (exact paths), DECISIONS (each with the option you rejected and why),
    ASSUMPTIONS (marked UNVERIFIED, with how I'd check them), NOT DOING, and
    DONE (a command I can run). Read the files first. Flag anything that could
    go either way instead of picking silently. If I reject part of it, give me
    the revised plan in full.
j / k to move between lessons