In my previous post I wrote about five principles for effective collaboration with AI. The first principle — think first, code later — deserves its own story.

Most developers open Claude Code, type a prompt, and let it build. For a small utility file that works fine. But as soon as you ask for a feature of any size, you get code that’s technically correct but doesn’t fit. Wrong patterns, different naming, a structure no one on your team recognizes. You spend more time correcting than you saved.

The problem isn’t that the AI codes poorly. The problem is that you let it run before it knows where it’s going. Plan Mode flips that around. In this mode, Claude Code first reads your codebase, analyzes existing patterns, and proposes an approach — before writing a single line of code. The difference: you don’t get code that works, you get code that fits.

Every time an AI writes code, it makes dozens of decisions. Which pattern do I use? Do I create a new table or extend an existing one? Polling or events? Controller or Minimal API? Without a plan, those choices are invisible — they’re hidden in the code it delivers. You don’t discover them until you open the pull request. And by then, course-correcting is expensive: throw away code, start over, or — worse — just leave it.

Plan Mode doesn’t change whether the AI makes assumptions. It changes when you see them. In a plan, those assumptions are spelled out as proposals. “I want to create a new table.” “I’ll use a controller.” Those are moments where you can say: no, we do that differently. Course-correcting in a plan takes seconds. Course-correcting in code takes hours.


Without a plan

Say your team is building an ordering platform in ASP.NET Core. A request comes in: users want to receive notifications when the status of their order changes. You open Claude Code and type:

“Build a notification system for order status changes.”

Claude Code gets right to work. No questions, no analysis — it delivers working code. But look at what it produces:

  • It creates a standalone NotificationService with its own logic — while your team uses MediatR for command/query separation and everything runs through handlers.
  • It creates a new Notifications table with its own migration — while your existing AuditLog already tracks events and can be extended.
  • It implements a polling mechanism that checks the database every 30 seconds — while you have an event-based architecture with IEventHandler<T>.

And then the structure. Your entire API is built with Minimal APIs. No controllers, no [Route] attributes. But Claude delivers this:

[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
    private readonly NotificationService _service;

    public NotificationsController(NotificationService service)
    {
        _service = service;
    }

    [HttpGet("{userId}")]
    public async Task<IActionResult> GetNotifications(int userId)
    {
        var notifications = await _service.PollForUpdates(userId);
        return Ok(notifications);
    }
}

Technically correct. Compiles. Does what you asked. But it doesn’t fit your project in any way. A colleague seeing this in a pull request would think it was copied from a different project.

The problem isn’t that Claude gets it wrong — it doesn’t know what “right” means in your project. Without a plan, it lacks the context to make the right choices.


With a plan

Same scenario, same feature — but a different approach. This time you give Claude context and ask it to think first.

Activating Plan Mode

You open Claude Code and write a prompt that gives direction:

“We want a notification system for status changes. Go into plan mode and first analyze how we handle events in /Application/EventHandlers. Look at our existing architecture before proposing anything.”

Claude Code switches to Plan Mode — it reads and analyzes before writing anything.

Explore

Claude dives into your codebase and comes back with findings:

I’ve analyzed the codebase. Here’s what stands out:

  • In Application/EventHandlers/ I find a consistent IEventHandler<T> pattern. All domain events are handled through this interface.
  • MediatR is used for command/query separation — commands go through IRequestHandler, queries through IRequestHandler<TRequest, TResponse>.
  • In the data layer there’s an AuditLog entity that already tracks events with fields like EntityId, Type, and Message. This is used project-wide for traceability.

The key: Claude understands your patterns before proposing anything. It has read, not guessed.

The plan

Based on that analysis, Claude comes up with a plan:

  1. New OrderStatusChangedEvent that fits the existing event pattern
  2. A NotificationHandler that implements IEventHandler<OrderStatusChangedEvent>
  3. Extend the existing AuditLog entity with notification fields — no new table
  4. A Minimal API endpoint for fetching notifications, via a MediatR query

No new system, but an extension that fits what’s already there.

The result

You approve the plan. Claude implements.

The event handler:

public class NotificationHandler : IEventHandler<OrderStatusChangedEvent>
{
    private readonly IMediator _mediator;

    public NotificationHandler(IMediator mediator)
    {
        _mediator = mediator;
    }

    public async Task Handle(OrderStatusChangedEvent @event)
    {
        await _mediator.Send(new CreateAuditLogCommand
        {
            EntityId = @event.OrderId,
            Type = "notification",
            Message = $"Order status changed to {@event.NewStatus}"
        });
    }
}

The API endpoint:

app.MapGet("/api/notifications/{userId}", async (int userId, IMediator mediator) =>
{
    var result = await mediator.Send(new GetNotificationsQuery(userId));
    return Results.Ok(result);
})
.WithName("GetNotifications")
.WithTags("Notifications");

No controller, no polling, no standalone service. The same feature as in the previous example — but now built as if your own team wrote it.

Claude runs dotnet build and dotnet test — all green.


Not for everything

Plan Mode isn’t for everything. A bugfix with a clear stack trace, a typo, a config change — you don’t need a plan for that. That’s overhead with no added value.

Rule of thumb: if you can already picture the result, you don’t need a plan. If you’re unsure about the approach, you do.

Think first

The AI is always going to make choices. The question isn’t whether, but when you see them. In a plan you read: “I’m going to create a new NotificationService.” Then you can say: no, use our existing handlers. In code you only discover that same decision as a complete implementation you have to throw away.

The best code is code that looks like your team wrote it. Plan Mode is how you get there — not by typing more, but by making the assumptions visible before they end up in your codebase.

Next time you build a feature, start with: “Go into plan mode and first analyze how we do this in the existing codebase.” You’ll notice the difference.