Working inside Cursor / Claude Code / Aider / Copilot

Aider: SEARCH/REPLACE blocks

4 min read

Aider is a CLI tool that pairs an LLM with your local repo. It applies model output to files using a specific format — SEARCH/REPLACE blocks — that's both human-readable and easy for a script to apply. Even if you don't use Aider, the format is a useful prompt-engineering primitive: it forces the model to commit to exact existing text before proposing the change.

The format is rigid:

filename.ext
<<<<<<< SEARCH
(exact existing lines)
=======
(new lines)
>>>>>>> REPLACE

The model has to reproduce the existing lines byte-for-byte. If it gets a single character wrong, the apply step fails. That failure is a feature, not a bug — a wrong SEARCH block tells you the model misread the file, and you'd rather know that before the change is applied than after.

Here's a small task done in this style:

Apply this change to src/server.py using a SEARCH/REPLACE block:

Goal: convert the synchronous fetch_user call into an async one and add a 5-second timeout.

Current:

def get_profile(user_id: str):
    user = fetch_user(user_id)
    return {"id": user.id, "name": user.name}

Output ONLY a SEARCH/REPLACE block in the format: src/server.py <<<<<<< SEARCH (exact existing lines) ======= (new lines) >>>>>>> REPLACE

Claude Sonnet 4.5 returned:

src/server.py
<<<<<<< SEARCH
def get_profile(user_id: str):
    user = fetch_user(user_id)
    return {"id": user.id, "name": user.name}
=======
async def get_profile(user_id: str):
    user = await asyncio.wait_for(fetch_user(user_id), timeout=5.0)
    return {"id": user.id, "name": user.name}
>>>>>>> REPLACE

Captured from Claude Sonnet 4.5 (claude-sonnet-4-5) on 2026-04-27. Re-runs may differ slightly.

The block is paste-ready into Aider, and it's also human-readable. A reviewer can scan the SEARCH section, scan the REPLACE section, and confirm the change without running the tool.

Three things SEARCH/REPLACE forces that a unified diff doesn't:

PropertyWhy it matters
Exact existing linesThe model must read the file accurately before proposing changes
Single-anchor matchingApply fails on ambiguous matches, surfacing latent issues
No line-number dependenceThe block works even if surrounding lines have shifted

The third property is what makes SEARCH/REPLACE so robust in long-running sessions. A unified diff with line numbers becomes invalid the moment another change shifts those lines. A SEARCH block remains valid as long as the content it's searching for hasn't changed.

The Aider apply loop:

The constraint to add for safety: when the change is large, ask for one block per logical unit, not one giant block:

If the change spans multiple logical edits (e.g., function signature + body + caller updates), output one SEARCH/REPLACE block per edit.

Multiple small blocks fail independently. If one fails to apply, you keep the rest. One giant block fails atomically — all or nothing — which is rarely what you want during iterative development.

A note on the missing import: in the captured output, the model used asyncio.wait_for without adding import asyncio at the top of the file. That's a real gap — the change doesn't compile as-is. The fix is a follow-up SEARCH/REPLACE block adding the import, or a stricter prompt: "Include any imports the change requires; if an import is missing from the file, add a separate SEARCH/REPLACE block to add it."

This is the value of working with Aider's format even outside Aider. The format makes failures visible.

Next up: planning prompts for Claude Code-style agents. :::

Quiz

Module 5: IDE & Tool Prompts

Take Quiz
Was this lesson helpful?

Sign in to rate

FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.