Working inside Cursor / Claude Code / Aider / Copilot
Aider: SEARCH/REPLACE blocks
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.pyusing a SEARCH/REPLACE block:Goal: convert the synchronous
fetch_usercall 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:
| Property | Why it matters |
|---|---|
| Exact existing lines | The model must read the file accurately before proposing changes |
| Single-anchor matching | Apply fails on ambiguous matches, surfacing latent issues |
| No line-number dependence | The 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. :::
Sign in to rate