Bug Fixing Prompt
Use this prompt when fixing bugs to ensure surgical, focused fixes without scope creep.
Prompt
You are fixing bugs only. This is not a refactoring session. This is not a feature addition. This is surgical bug fixing.
Bug to fix: [description of the bug]
Steps to reproduce: [if known]
Expected behavior: [what should happen]
Actual behavior: [what actually happens]
Bug Fixing Constraints
You MUST:
- ✅ Fix only the root cause of this specific bug
- ✅ Add or update tests to prevent regression
- ✅ Preserve all existing behavior except the bug
- ✅ Keep changes minimal and focused
You MUST NOT:
- ❌ Refactor unrelated code
- ❌ “Clean up” surrounding code
- ❌ Add new features, even small ones
- ❌ Change coding style of existing code
- ❌ Fix other bugs you notice (file them separately instead)
- ❌ Make “while we’re here” improvements
Bug Fixing Process
1. Understand the Root Cause
Before changing any code:
- Reproduce the bug (if possible)
- Identify the root cause, not just symptoms
- Trace the bug to the specific code location
- Understand why the current code is wrong
Ask yourself:
- Why does this bug exist?
- Is this a logic error, edge case, race condition, etc.?
- What assumptions were incorrect?
- Could this bug exist elsewhere with the same pattern?
2. Debug Logging Strategy
CRITICAL: Be VERY generous with debug logging when investigating bugs.
Don’t be cautious or conservative with logging. The more information, the better.
What to Log
Log comprehensively at every step:
// ✅ EXCELLENT - Comprehensive context
function processImport(data: ImportData) {
console.log('[IMPORT] Starting import:', {
recordCount: data.records.length,
source: data.source,
timestamp: Date.now()
});
for (const record of data.records) {
console.log('[IMPORT] Processing record:', {
id: record.id,
type: record.type,
hasRequiredFields: !!(record.name && record.email)
});
const result = validateRecord(record);
console.log('[IMPORT] Validation result:', { id: record.id, result });
if (!result.valid) {
console.log('[IMPORT] SKIPPING invalid record:', {
id: record.id,
errors: result.errors,
record
});
continue;
}
// ... more logging at each step
}
}
// ❌ INSUFFICIENT - Too cautious
function processImport(data: ImportData) {
console.log('Processing import');
for (const record of data.records) {
const result = validateRecord(record);
if (!result.valid) continue;
}
}
Log at these critical points:
- Function entry/exit - With full input/output context
- Before/after API calls - Request + response + timing
- Conditional branches - Log BOTH branches with relevant data
- Data transformations - Log before and after state
- Cache/state updates - Log what’s being updated and the new value
- Error cases - Log the full context that led to the error
Use Descriptive Prefixes
Make logs easy to grep:
console.log('[IMPORT-DEBUG] ...');
console.log('[CONTACT-UPDATE] ...');
console.log('[CACHE-MUTATION] ...');
console.log('[API-RESPONSE] ...');
Philosophy
It’s better to have 50 lines of debug output and quickly identify the issue than to add one log statement at a time and iterate slowly.
You can always remove logging later. You can’t retroactively see what was happening without it.
⚠️ Debug Code Can Introduce Bugs
CRITICAL: Debug code itself can cause issues, especially with React Query, Redux, and other systems that rely on immutability.
Dangerous patterns:
// ❌ DANGEROUS - Mutates cached object
console.log('Contact:', contact);
contact.debugFlag = true; // Modifies React Query cache!
contact.roles.push('DEBUG'); // Breaks immutability!
// ❌ DANGEROUS - .sort() mutates arrays
console.log('Sorted:', contacts.sort(...)); // Mutates cached array!
// ❌ DANGEROUS - Modifying during iteration
const processed = contacts.map(c => {
c.processed = true; // Mutates original!
return c;
});
Safe patterns:
// ✅ SAFE - Read-only access
console.log('Contact:', { ...contact });
// ✅ SAFE - Clone before mutating
console.log('Sorted:', [...contacts].sort(...));
// ✅ SAFE - Create new objects
const processed = contacts.map(c => ({
...c,
processed: true // New object
}));
Why this matters:
- React Query caches by reference - mutations affect ALL components
- Mutating arrays (.sort(), .push(), .splice()) is especially dangerous
- Creates “phantom bugs” that vanish when debug code is removed
Rule: Never mutate objects you’re debugging - only read, never write.
3. Root Cause vs Symptoms
CRITICAL LESSON: Trace data flow UPSTREAM to the entry point. Don’t chase symptoms downstream.
❌ Inefficient: Whack-a-Mole with Symptoms
// Round 1: Add defensive check
{contact.name?.slice(0, 1) || '?'}
// Round 2: Error appears elsewhere, add another check
{typeof contact.email === 'string' ? contact.email : ''}
// Round 3: More defensive checks...
{Array.isArray(contact.roles) ? contact.roles.join(', ') : ''}
// Result: 20+ defensive checks scattered everywhere, error still persists
Problem: You’re treating symptoms (unsafe rendering) not the root cause (malformed data).
✅ Efficient: Trace Data Flow Upstream
Key Question: “Where could invalid data GET INTO the system?” (not “where are we using it unsafely?”)
Trace the flow backward:
JSX Render → Component Props → React Hook → React Query Cache → API Response
↑ ↑
ERROR HERE FIX IT HERE!
Also fed by:
↑
Inline edits, form submissions, optimistic updates
Real-world example:
// Stack trace showed: setData → batch → setTimeout
// This pointed to React Query cache mutations, not rendering
// ❌ I first looked at: JSX rendering code (wrong!)
<div>{contact.name.slice(0, 1)}</div>
// ✅ Should have looked at: What's calling setData?
// Found in use-inline-edit.ts:
updateContactCache({
...contact,
[field]: value // ← value: unknown (TypeScript warning!)
})
Red Flags Pointing to Entry Point Issues
- TypeScript
unknownoranytypes - Data that could be “anything” - Stack trace shows cache/state updates - Not rendering code
- Defensive fixes don’t stop the error - You’re not at the source
- Error appears in multiple components - Shared upstream data is bad
The Right Debugging Flow
For data-related errors:
- Read the stack trace carefully
setData/setState/dispatch→ Cache/state mutation issue- Component render → Check data source, not just render code
- Trace data backward to its origin
- Component crashed → What data is it using?
- Where does that data come from? (props, hook, query)
- Where does THAT come from? (API, cache, state)
- Keep going until you find the entry point
- Validate at entry point, not at every usage
// ❌ BAD: Defensive checks everywhere function ContactCard({ contact }: { contact: Contact }) { const name = typeof contact.name === 'string' ? contact.name : 'Unknown'; const email = typeof contact.email === 'string' ? contact.email : ''; // ... 20 components with similar checks } // ✅ GOOD: Sanitize once at the entry point function useContacts() { return useQuery({ queryFn: async () => { const response = await api.contacts.getAll(); return response.data.map(sanitizeContact); // ← Fix here } }); } // Now all components can trust the data function ContactCard({ contact }: { contact: Contact }) { return <div>{contact.name}</div>; // Safe! } - Look for TypeScript warnings
value: unknown // ← RED FLAG! data: any // ← RED FLAG!
Ask the Right Questions
- ❌ “Where do we render
contact.nameunsafely?” -
✅ “Where could
contact.namebecome a non-string?” - ❌ “Let’s add
contact.field?.toString() ?? ''everywhere” - ✅ “Let’s ensure only valid Contact objects enter our cache”
One sanitization layer at entry points » Dozens of defensive checks at usage points
4. Design the Minimal Fix
Plan the smallest possible change:
- What’s the minimal code change to fix the root cause?
- Can this be fixed with a 1-line change? 5 lines? 10 lines?
- Are there alternative fixes that are simpler?
Avoid:
- Rewriting entire functions when changing one condition would work
- Introducing new abstractions to fix a simple bug
- Changing method signatures unless absolutely necessary
5. Write or Update Tests
Test requirements:
- Add a test that fails without the fix and passes with it
- Ensure existing tests still pass
- If there’s an edge case, add a test for it
- Make tests specific to this bug (not generic)
Test format:
test_bug_[issue_number]_[brief_description]:
# This test reproduces the bug from issue #123
# Expected: [correct behavior]
# Before fix: [buggy behavior]
[test code]
6. Implement the Fix
Implementation guidelines:
- Change only what’s necessary
- Match the existing code style (even if not ideal)
- Add comments only if the fix is non-obvious
- Preserve existing error handling
If the fix requires larger changes: Stop and discuss whether this is really just a bug fix, or if it’s a design issue that needs architectural changes.
7. Verify the Fix
Check that:
- Bug is fixed (verify manually)
- New/updated tests pass
- All existing tests still pass
- No new bugs introduced (especially edge cases)
- No invariants violated
- No regressions in related functionality
Output Format
Provide your fix in this format:
Root Cause Analysis
Location: [file:line]
Root cause: [clear explanation of why the bug exists]
Why it happened: [incorrect assumption, missing edge case, etc.]
The Fix
Changed files:
Diff:
[show the minimal diff]
Tests Added/Updated
New tests:
Updated tests:
Verification
- Bug no longer reproduces
- All tests pass
- No regressions observed
- Edge cases handled
Examples
✅ Good Bug Fix
Bug: User login fails when email has uppercase letters
Root cause: Email comparison is case-sensitive
Fix: One line change to lowercase email before comparison
- if user.email == provided_email:
+ if user.email.lower() == provided_email.lower():
Test: Added test for uppercase email login
Scope: 1 line of production code, 5 lines of test code
❌ Bad Bug Fix (Scope Creep)
Bug: User login fails when email has uppercase letters
Fix: Rewrote entire authentication system to use a new library, refactored user model, changed database schema, added password strength validation, updated UI components
Why this is bad: The bug was a simple comparison issue. All these changes introduce risk and are unrelated to the actual bug.
When to Stop and Ask
Stop and ask for guidance if:
- The minimal fix would require changing > 50 lines of code
- The fix requires changing public APIs
- The fix reveals a deeper architectural issue
- Multiple approaches exist with significant trade-offs
- The fix might break backwards compatibility
In these cases, the “bug” might actually be a design issue that needs broader discussion.
Remember
Surgical precision beats comprehensive improvement.
A focused bug fix that changes 3 lines is better than a “clean” refactor that changes 300 lines—even if the refactor is objectively better code. Bug fixes should be:
- Minimal: Smallest possible change
- Obvious: Easy to review and verify
- Safe: Low risk of introducing new bugs
- Testable: Covered by regression tests
The best bug fix is the one you can confidently deploy without fear.