Lesson 1.3: Make Your First AI-Assisted Code Edit Using the Diff Loop
THE NAIVE APPROACH TRAP
A developer comfortable with Claude.ai chat might attempt this lesson by:
Opening Claude in a browser, pasting a code snippet
Asking Claude to fix it, getting back a rewritten version
Manually copying the diff, applying it to their editor
Repeating for the next file
The failure: This workflow loses Claude Code's core valueβthe diff validation loop. Without it:
Silent wrong-outputs: You accept changes without reviewing intent-to-code alignment. A variable rename might ripple through 5 files unnoticed.
No rollback: Once you copy code, you've committed mentally. The diff loop lets you say "no" and iterate. Chat forces a full redo.
Token burn: Each manual handoff resets context. "Remember when I said the config format was TOML?" is now lost; Claude re-explains it. The diff loop keeps session state.
No atomic verification: Chat gives you code; you run tests separately. The diff loop integrates test feedback into the next suggestion.
THE FAILURE MODE
Claude Code's diff loop fundamentally changes what "accepting changes" means:
When you run claude --chat and ask Claude to rewrite a function, Claude outputs a full response blob. You read it, copy it, paste it. Your brain is the merge tool. Mistakes in the transition are invisible until tests fail.
When you run claude (no flags) in interactive mode on a file:
Claude reads the file's full content via
/addClaude examines your intent from your prompt
Claude generates a unified diff, not raw code
The CLI prints this diff to stdout with context lines
You accept (
y), reject (n), or edit (e) before any write happensIf you accept, the CLI applies the diff and loops for the next edit
The non-obvious mechanics:
The diff is idempotentβapplying it twice fails safely, not silently
Rejection is stateless: Claude stays in the conversation; you're iterating on the same file, not starting over
The context window includes full file content plus your entire edit historyβClaude learns from what you rejected last time
Exit code is non-zero if the diff fails to apply, catching merge conflicts in scripts
Failure points in naive implementations:
Running
claude --printwithout capturing the exit code, so your CI doesn't know a diff failedAccepting diffs in a loop without reading them ("just let Claude do it"), then discovering mid-sprint that the code no longer matches your architecture
Not using CLAUDE.md to anchor context, so each session Claude forgets your codebase's conventions
Assuming
--allowedToolsdefaults allow all operationsβit doesn't; you need to explicitly permit writes
THE CLAUDEFORGE ARCHITECTURE
This lesson is the first hands-on checkpoint in Module 1. It connects:
Backward to Lesson 1.1β1.2: You've installed Claude Code and authenticated with claude login. Your .claude/auth.json holds your session token (Pro subscription, no API key).
Forward to Lesson 1.4: You'll write a production CLAUDE.md that anchors Claude's understanding across future edit sessions. This lesson uses a minimal one.
Cross-reference to Module 2: Lessons 2.1β2.2 will teach /add, /remove, and @-file as deliberate context scoping. This lesson shows why that matters: the diff loop only works when Claude has tight, explicit context.
Critical files in this lesson:
CLI flags introduced:
claude --chat: Interactive prompt loop (used in lessons 1.1β1.2)claude(no flags): File-focused interactive mode with diff loop (this lesson)--allowedTools: Restricts which operations Claude can perform (default: read-only)--print: Outputs diffs to stdout instead of asking for acceptance (used in Module 3)
CLAUDE.md sections Claude weights at session start:
## Context: Your codebase's language, framework, and conventions## Recent Changes: What you've been working on (Claude uses this to avoid suggesting redundant edits)## Known Issues: Constraints Claude should respect
IMPLEMENTATION DEEP DIVE
How the Diff Loop Preserves Context Across Edits
When you start an interactive session with claude src/server.py, Claude's session state includes:
Full file content (from
/addimplicit at start)System context (from CLAUDE.md)
Edit history β a record of every diff you accepted, rejected, or edited
This history is ephemeral per session, not saved to disk. If you close the session, it's gone. This is intentionalβit forces you to write important context to CLAUDE.md for cross-session continuity.
Why Diff Presentation Matters
A unified diff shows:
Three critical properties:
Reviewable: You see the exact change before it lands.
Safe to reject: Saying
ndoesn't delete the file; Claude's context stays intact for the next attempt.Applicability: The CLI verifies the patch applies cleanly. If your file has changed since the edit started, the diff fails with exit code 1, not a silent merge conflict.
The --allowedTools Permission Model
By default, claude starts with --allowedTools=read-only (even though this is not in the help text). This means:
Claude can read files via
/addClaude cannot execute
touch,rm, or shell commandsClaude can suggest diffs; you apply them manually
To permit Claude to write directly (not recommended for beginners; used in Module 3 pipelines):
Why this matters for lesson 1.3: You're explicitly reviewing and accepting each diff. This is the safe, human-centered default. Don't change it yet.
CLAUDE.md Parsing at Session Start
When you run claude src/server.py, the CLI:
Looks for
./CLAUDE.mdin the current or ancestor directoriesParses it as Markdown, extracting key sections by heading
Passes the full content to Claude's system prompt
Example minimal CLAUDE.md for this lesson:
Claude sees this at the start of your session and weighs it heavily in every response. A vague CLAUDE.md ("Fix bugs") produces vague diffs. A specific one ("Return ISO 8601 timestamps in UTC") produces precise, reviewable changes.
PRODUCTION READINESS
Metrics for Real Deployments
Per-session observability:
Diff acceptance rate: How many diffs did you accept vs. reject? >80% suggests Claude is well-calibrated to your codebase. <60% suggests CLAUDE.md is too vague.
Edits per fix: How many "uh, actually can you change it to..." iterations happen per feature? Ideal: 1β2. >5 suggests you're not being specific enough in your initial prompt.
Token usage from
/status: A 10-file refactor should not burn >2.5M tokens. If it does, your context is bloated (use@-filemore precisely).
Post-merge signal:
Test pass rate: If you're accepting diffs without running tests, you'll merge broken code. Always run
pytestbefore accepting.Revert rate: If PRs using Claude Code diffs get reverted >5% of the time, you're not reviewing thoroughly.
Failure Signals to Monitor
claudecommand hangs on diff approval prompt: Your/addcontext is over 50KB; Claude's response is slow. Reduce context scope.Exit code 1 on
--printin CI: The diff failed to apply. Someone modified the file between Claude's generation and the CLI's application. Use a pre-check step.CLAUDE.md ignored: You wrote a CLAUDE.md, but Claude keeps suggesting Python 2 patterns when you said "Python 3.11+". Restart your session; the CLAUDE.md is only read at session start.
STEP-BY-STEP GUIDE
Prerequisites
Node.js 20 LTS (verified:
node --versionβ₯ 20.0)Python 3.11+ (verified:
python3 --versionβ₯ 3.11)Claude Code CLI latest stable:
Claude.ai Pro subscription with
claude logincompleted in Lesson 1.1Verify:
cat ~/.claude/auth.jsonshould exist and be valid JSONDO NOT set
ANTHROPIC_API_KEYfor this module; it overrides your Pro billing
macOS 14+, Ubuntu 22.04, or Windows 11 WSL2
Execution
Step 1: Clone and navigate to the lesson workspace
Step 2: Examine the intentionally imperfect code
Expected content:
This code is correct but inelegant: the health_check() function is separate from the endpoint, and we're missing error handling and structured logging (per CLAUDE.md's "recent changes" section).
Step 3: Read the minimal CLAUDE.md
Expected content anchors Claude's suggestions:
Step 4: Start an interactive session
Expected prompt:
Step 5: Add the server file to context
Expected output:
Verify with /status:
Output shows token estimate:
Step 6: Make a specific edit request
Claude responds with a diff:
Review the diff carefully. Does it match your intent? Does it follow the "async everywhere" pattern from CLAUDE.md?
Step 7: Accept or reject the diff
Expected output:
Your server.py is now updated on disk.
Step 8: Verify the change locally
Exit the session. Run tests to confirm the change works:
Expected test output (assuming the test file exists):
Verification
Check that the file was modified:
Should show your accepted diff.
Confirm Claude.ai recognizes the skill is active:
Navigate to claude.ai in your browser β Settings β Skills. You should see "Claude Code" listed under "Connected Tools" (not under "Available Skills"βClaude Code is built-in, not a separate Skill).
If you're using VS Code with the Claude Code extension:
Open the sample-api folder
Create a new file:
endpoints.pyCmd/Ctrl+K to open inline chat
Type: "Add a /metrics endpoint that logs request count"
The inline chat uses the same diff loop as the CLI
HOMEWORK
Task: Apply this to your own codebase (30β60 min)
Pick a real file from your team's repository (or a personal project) that you've wanted to refactor but have been putting off. Aim for <200 lines.
Write a 3-line CLAUDE.md at your repo root, covering:
Language / framework version and any recent architectural changes
One specific, measurable change you want Claude to make (not "improve the code")
Run
claudeat the repo root,/addthe file, and request the change.Accept the first diff only if it matches your intent exactly. If not, reject and iterate. Aim for 1β2 acceptances, not more.
Run your test suite before committing.
Deliverable you keep: A commit message documenting what changed, why Claude's suggestion was better than your first instinct, and what you'd do differently next time.
Why this matters: This is the moment you'll internalize why the diff loop exists. You'll feel the difference between "Claude generated code I copied" and "I reviewed and accepted a specific, bounded change."
APPENDIX: Troubleshooting
Q: Claude suggests code that's already in the file.
Cause: Your
/addcontext doesn't include the full file, or you edited the file outside this session.Fix: Exit with
/done, restart withclaude src/myfile.py, and/add src/myfile.pyfresh.
Q: Diff fails to apply (exit code 1).
Cause: Someone or something modified the file between Claude's generation and your acceptance.
Fix: Reject the diff, let Claude re-generate with the updated file content.
Q: CLAUDE.md isn't helpingβClaude still ignores my style guidelines.
Cause: CLAUDE.md is read at session start only. Changes made mid-session are ignored.
Fix: Exit and restart the session. Or add a reminder to your prompt: "Remember, we're using async/await everywhere."
Q: I set ANTHROPIC_API_KEY and now Claude Code uses my API credits instead of my Pro subscription.
Fix: Unset it:
unset ANTHROPIC_API_KEY, then exit and restartclaude. Pro billing resumes automatically.