TL;DR
I run an autonomous coding agent that ships code with almost no human review. That's fast, and it's also how I've nearly lost work to a bad git reset more than once. Switching risky steps over to Claude Code's Plan Mode — where the agent has to show its plan before it touches anything — turned "trust me" into "let me check first." Here are 5 concrete times that saved me, and 2 times I turned it back off.
The Problem
If you've run an AI coding agent unattended for more than a few weeks, you already know the failure mode: it's not that the agent is dumb, it's that it's confident. It reads three files, forms a theory about what's going on, and acts on that theory — even when the theory is wrong.
Most of the time that's fine. The agent fixes the bug, the tests pass, you move on. But every so often the theory is wrong in a way that's expensive to undo: it decides a branch is "stale" and resets it, it decides a config file is "generated" and rewrites it, it decides the fix for a failing test is a 12-file refactor instead of a one-line guard clause.
I'd been treating "let the agent run" and "review everything" as the only two modes available. Full autonomy is fast but occasionally destructive. Full review is safe but defeats the entire point of an autonomous agent — I'm back to reading every diff, which is the job I was trying to get out of.
By the time I actually did something about it, I could count three incidents in two months where "the agent's theory was wrong" cost me real time: one near-miss with a force-push that would've clobbered a teammate's branch (caught by luck, not process), one config file rewritten from a stale assumption about its schema, and one multi-hour debugging session chasing a bug the agent had itself introduced two commits earlier while "cleaning up." None of these were catastrophic. All of them were avoidable if I'd seen the intent before the action.
What I actually needed was a third option: let the agent think out loud before it acts, and only step in when the plan itself looks wrong. Claude Code already ships this as Plan Mode, and I'd been ignoring it because it felt like extra friction. It isn't. It's the cheapest insurance I've added to the whole setup.
How I Solved It
Plan Mode changes what tools the agent can use. In normal mode, it can read files, edit them, and run shell commands as needed. In Plan Mode, it can still explore — read files, search the codebase, run read-only commands — but it can't edit anything or run anything destructive. Instead, it has to write up what it intends to do, and a human (or a gate you build) approves the plan before execution starts.
flowchart LR
A[Task assigned] --> B{Plan Mode?}
B -- yes --> C[Agent explores: read, grep, run tests]
C --> D[Agent writes a plan]
D --> E{Plan approved?}
E -- yes --> F[Agent executes with full tool access]
E -- no / edited --> D
B -- no --> F
You can trigger it interactively (toggle it mid-session), or you can make it the default for a whole project in settings.json:
{
"permissions": {
"defaultMode": "plan"
}
}
With that set, every session starts in planning mode, and the agent has to earn write access by presenting a plan first. For a fully unattended agent, I don't have a human sitting there to click "approve" — so I built a lightweight gate that inspects the plan text for red flags (broad file globs, reset/force/rm -rf, changes outside the task's declared scope) and only lets low-risk plans through automatically. Anything that trips the gate gets logged and skipped rather than force-approved. That single filter turned Plan Mode from "manual review required" into "automatic review, human review only on the interesting cases" — which is the whole point.
The gate itself is nothing fancy — it doesn't need to be. Something like this catches the majority of the scary cases:
DANGEROUS_PATTERNS = [
r"\bgit\s+reset\s+--hard\b",
r"\bgit\s+push\s+--force\b",
r"\brm\s+-rf\b",
r"\bDROP\s+TABLE\b",
r"\.env\b",
]
def needs_human_review(plan_text, task_scope_files):
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, plan_text, re.IGNORECASE):
return True, "matched dangerous pattern"
touched = extract_referenced_files(plan_text)
out_of_scope = [f for f in touched if f not in task_scope_files]
if len(out_of_scope) > 2:
return True, f"plan touches {len(out_of_scope)} files outside declared scope"
return False, None
It's deliberately conservative — false positives just mean an extra plan gets logged for me to skim later, while false negatives mean something ships that shouldn't have. Given that asymmetry, I'd rather the gate flag too much than too little.
Here's roughly what a caught plan looks like in practice — the agent was asked to "clean up the feature branch before merging," and its first plan was:
Plan:
1. Run `git log --oneline main..HEAD` to review commits
2. Run `git reset --hard main` to drop the branch back to a clean state
3. Cherry-pick the 3 commits that actually matter
4. Force-push the cleaned branch
Step 2 is the problem. The branch had uncommitted analysis notes the agent itself had written earlier in the session and hadn't committed yet — git reset --hard would have deleted them with no recovery path. In normal mode, that step just runs. In Plan Mode, it's a line of text I get to read first.
Lessons Learned
1. It catches destructive git operations before they run, not after. The git reset --hard example above is real, and it's the single biggest reason I now default risky sessions into Plan Mode. The cost of catching this in a plan is reading one sentence. The cost of catching it after the fact is however much unsaved work you just lost.
2. It surfaces scope creep while it's still cheap to redirect. I've had "fix this failing test" turn into a plan that touched 12 files across 3 modules, because the agent decided the real fix was a refactor. Maybe it was right! But finding that out from a plan takes 10 seconds; finding it out from a 400-line diff takes 10 minutes, and by then the agent has already burned the tokens writing code you might reject anyway.
3. It exposes wrong assumptions before they cost you a debugging session. More than once the agent's plan referenced a config path or a function signature that didn't actually exist in the codebase — it pattern-matched from a similar project it had seen before. In Plan Mode, that shows up as a plan step you can immediately tell is wrong. In normal mode, it shows up as a confusing runtime error twenty minutes later, and you spend that twenty minutes debugging the symptom instead of the actual wrong assumption.
4. Anything touching more than ~3 files is worth planning first, no exceptions. I don't have a precise science here, but empirically, single-file fixes are almost never where the damage happens. Multi-file changes are where an agent's mental model of "what this task requires" and my mental model start to diverge, and Plan Mode is the cheapest point to catch that divergence.
5. It's genuinely not worth it for trivial one-liners. I tried defaulting every session into Plan Mode for a week, and it was the wrong call. Typo fixes, one-line guard clauses, updating a version string — these don't benefit from a planning step, they just get slower. Now I only force Plan Mode for tasks that are multi-file, involve git history rewrites, or touch anything I've tagged as sensitive (CI config, deploy scripts, database migrations). Everything else runs straight through, and that split has been the right balance for months now.
The two times I turned it back off were both variations of lesson 5 — small, well-scoped, single-file tasks where the plan step added latency and told me nothing I couldn't have inferred from the diff itself. Plan Mode is a filter, not a religion; using it everywhere is just review-everything with extra steps.
What's Next
Right now my risk gate is a simple keyword filter on the plan text, and that's honestly a little embarrassing — it'll miss a cleverly-worded destructive step and it'll false-positive on a plan that happens to mention "force" in a comment. The next iteration is scoring plans against the task's declared scope (does this plan only touch files related to the stated task, yes or no) instead of pattern-matching on scary words. I'm also looking at whether I can auto-approve plans that match a pattern I've explicitly greenlit before (e.g. "always safe to update this changelog file") so the gate gets less noisy over time instead of just accumulating more keywords.
Wrap-up
If you're running any AI coding agent with meaningful autonomy — even just "let it finish the task without me watching every step" — turning on Plan Mode for anything multi-file or git-history-touching is a five-minute change that will save you from at least one bad afternoon. Start with the defaultMode: "plan" setting on your riskiest project and see what shows up in the first few plans; I'd bet you catch something in the first week.
If you found this useful, follow me here on Dev.to — I'm writing up lessons from this agent build weekly. And if you've had your own "the plan caught it" moment, I'd genuinely like to hear about it in the comments.
Top comments (0)