The Setup: A “Simple” Macro Enhancement
After successfully building 5 derive macros that eliminated 4,700 lines of boilerplate, I wanted to add one more feature: auto-generated CRUD methods. The change seemed straightforward:The Cascade: 30 Commits in 24 Hours
Commit 508f43e:Hour 1: The First Errors
Commit cd7c3e1:Hour 2-3: The Pattern Emerges
Commit 23d0baf:9e0815f: replace self.client with client11fe195: add client creation to save_calendar methodb1a1ff0: batch fix organization_dynamodb.rs methodseb5e215: fix pipeline_dynamodb client callsa7fbb9b: fix remaining client() method calls
Hour 4-6: Type System Whack-a-Mole
This is where it got interesting. Each fix revealed new errors. Commit 8d6c763:- Old:
pk_for_id(tenant_id, capsule_id, id) - New:
pk_for_id(id)(macro infers tenant/capsule from entity)
9249626: fix pk_for_id and gsi method signatures7503f9f: add GSI methods and fix syntax errors47fd4ae: fix CrmError variants, field access errors3afc5a0: move contact methods to ContactRepository impl block3862c3e: fix E0425 errors (missing values) and self.client() calls
The Breaking Point: When I Stopped Trusting AI
Hour 7 (next morning): I found the AI session had made 6 more commits overnight (I left it running). Latest commit (f1ed1e7):The Fix: Human Intervention
What I did:- Stopped the AI session
- Read the original macro change (commit 508f43e)
-
Made a list of ALL changes the macro introduced:
- New method names (save → db_save)
- New factory pattern (client field → client() method)
- New error types (EventStoreError instead of RepositoryError)
- New dependency requirements (serde_dynamo)
- Fixed them systematically in 3 commits instead of 30:
What Went Wrong: AI’s Fix-in-Session Anti-Pattern
The Problem
When AI encounters a compilation error, it follows this pattern:- Read the error message
- Understand the immediate cause
- Fix that specific error
- Compile
- If more errors, repeat from step 1
- Each fix addresses symptoms, not root cause
- No holistic understanding of “what changed upstream”
- No batching of related fixes
- Creates more errors by partial fixes
The Example
Error cascade from macro change:- Recognize pattern: “All save() calls need to be db_save()”
- Fix ALL save() calls in one commit
- Recognize pattern: “All error types changed”
- Add unified error conversion
- Compile once → done
What I Learned: When to Stop Using AI
Red Flags That AI Is Stuck
Red Flag #1: Diminishing Returns Track errors fixed per commit:- Commits 1-5: Average 12 errors fixed each
- Commits 6-15: Average 4 errors fixed each
- Commits 16-30: Average 1 error fixed each (sometimes net increase!)
Red Flag #2: Repeated Fix Patterns If you see the same type of fix across multiple commits:
- “fix client() calls” (7 commits)
- “fix pk_for_id signature” (5 commits)
- “fix error variants” (6 commits)
Red Flag #3: Partial Fixes in Commit Messages Commit messages with “partial fix” or “fix remaining” indicate AI doesn’t have a complete solution. Examples from the cascade:
- “partial fix for organization_dynamodb.rs factory pattern”
- “fix remaining client() method calls”
- “batch fix organization_dynamodb.rs methods”
When to Use AI vs. Manual Intervention
- Use AI When
- Intervene Manually When
- The Decision Tree
Isolated errors:AI can fix: Remove the unused import. Done.
Systematic but understood errors:AI can fix: Add Clone derive to all 12 event types in one commit.
- Single file affected
- Error is self-contained
- Fix doesn’t affect other files
Systematic but understood errors:
- Pattern is clear
- AI successfully batches fixes
- Each fix reduces total error count
What We Learned: The Fix-in-Session Anti-Pattern
The Anti-Pattern
Fix-in-Session workflow:- Make change (e.g., update macro)
- Compilation errors appear (30+ errors)
- Ask AI: “Fix all compilation errors”
- AI fixes one error at a time
- Each fix potentially creates new errors
- Repeat until done (or stuck)
- AI lacks global view of the change
- Optimizes for immediate error, not root cause
- Creates intermediate broken states
- Risk of hallucinating fixes when stuck
The Better Approach: Understand-Then-Fix
Step 1: Understand the change- What did the macro modification actually change?
- What are ALL the breaking changes?
- Which files will be affected?
- Group errors by type (method name changes, type mismatches, missing imports)
- Identify pattern in each category
- Batch fix all method name changes in one commit
- Batch fix all type mismatches in another commit
- etc.
- Compile
- Run tests
- Verify no regressions
Real Example: How I Should Have Done It
The right approach for the CRUD macro change: Commit 1: Update macro (breaking change)- 31 commits (1 feature + 30 fixes)
- 24 hours
- Multiple intermediate broken states
The Meta-Learning: AI Isn’t Good at Debugging Its Own Changes
Why AI Struggles with Cascading Errors
Problem 1: No Mental Model of the System Human developer:- Knows that changing a macro affects all users of that macro
- Can predict which files will break
- Can grep for all usage sites before making the change
- Doesn’t build system-wide mental model
- Only sees errors after they appear
- Fixes reactively, not proactively
Problem 2: Optimizes for Local, Not Global Each error fix is locally optimal (fixes that specific error) but globally suboptimal (creates more errors elsewhere). Example:
client() methods instead of updating 7 call sites.
Problem 3: No “Step Back” Capability After 15 commits with diminishing returns, AI doesn’t think:
- “This approach isn’t working”
- “Maybe I should try a different strategy”
- “Let me understand the root cause first”
Principles Established
Principle 1: Breaking Changes Need Migration Plans
Principle 1: Breaking Changes Need Migration Plans
What we learned: Macro changes are breaking changes that affect multiple crates simultaneously.New rule: Before making breaking macro changes:
- List all affected crates
- Identify all breaking changes (method renames, type changes, etc.)
- Create migration checklist
- Fix all affected crates in a single atomic commit
- Never commit broken intermediate states
Principle 2: Monitor AI's Error-Fixing Progress
Principle 2: Monitor AI's Error-Fixing Progress
What we learned: If error count isn’t decreasing steadily, AI is stuck.Tracking metric: Errors fixed per commit
- Healthy: 5-10 errors fixed per commit
- Warning: 2-4 errors fixed per commit
- Critical: Less than 2 errors per commit
Principle 3: Batch Fixes Beat Incremental Fixes
Principle 3: Batch Fixes Beat Incremental Fixes
What we learned: 30 small commits are worse than 1 comprehensive commit for systematic changes.When to batch:AI limitation: AI doesn’t use these batch tools effectively. Prefers file-by-file fixes.
- Method rename across many files
- Type signature changes
- Dependency updates
- Error handling pattern changes
Principle 4: AI Needs Constraints for Error Fixing
Principle 4: AI Needs Constraints for Error Fixing
What we learned: Unconstrained “fix all errors” leads to hallucination and inefficiency.Better prompt:This forces AI to: Think strategically about batching fixes instead of fixing one at a time.
Principle 5: Verify Generated Code Before Using
Principle 5: Verify Generated Code Before Using
What we learned: The cascade started because I didn’t verify the macro’s generated code before
migrating all entities.New practice:
- Make macro change
- Use
cargo expandon ONE entity - Review generated code
- Test with that entity
- Only then migrate other entities
The Recovery: How I Fixed It
After recognizing AI was stuck, I took over: Step 1: Understand the root cause (30 minutes) Read the macro change commit carefully. Listed all breaking changes:save()→db_save()get()→db_get()clientfield →client()methodRepositoryError→EventStoreError- New dependencies required
Metrics: The Cost of the Cascade
- AI Approach
- Human Recovery
- Lesson Learned
Commits: 31 (1 feature + 30 fixes)Time: 24 hoursFinal state: Still had 14 errorsErrors introduced: ~40 new errors during fixing processToken usage: 850k tokens ($13)Developer frustration: High
Code Example: The Hallucinated Fix
Here’s the most egregious example of AI hallucination during the cascade: The error:CrmError::Repositoryis NOT a tuple variant with a String- Lost the actual error details from EventStoreError
The Surprising Discovery: AI Can Prevent This
After recovering from the cascade, I asked a fresh Evaluator session:-
Impact Analysis:
- Run
cargo treeto find all crates using the macro - Grep for all usage sites across workspace
- Estimate affected lines of code
- Run
-
Breaking Change Detection:
- Compare old vs new generated code (cargo expand)
- List method signature changes
- List type changes
- List new dependencies
-
Migration Plan:
- Create checklist of required updates per crate
- Batch fixes by category (method renames, type updates, etc.)
- Prepare workspace-wide test command
-
Atomic Commit Strategy:
- Update macro
- Update ALL downstream crates
- Verify workspace compilation
- Commit atomically
Takeaways
For breaking changes:- Always create migration plan first
- Fix all affected sites atomically
- Never commit intermediate broken states
- Use batch tools (sed, sd, rg) for systematic renames
- Monitor error-fixing progress (errors fixed per commit)
- Intervene when progress stalls (< 3 errors per commit)
- Use AI for planning prevention, human for reactive fixes
- Fresh AI sessions for post-mortem analysis