Bug Fixing Workflow
Complete guide to identifying, isolating, fixing, and validating bug fixes using agentful's systematic workflow.
Overview
The Bug Fixing workflow takes a bug report and systematically resolves it through reproduction, root cause analysis, fix implementation, regression testing, and validation.
What This Workflow Delivers
- ✅ Bug reproduction and isolation
- ✅ Root cause identification
- ✅ Minimal, targeted fix
- ✅ Regression tests to prevent recurrence
- ✅ All validation gates passing
- ✅ Documentation of the fix
Typical Timeline
| Bug Severity | Time | Iterations |
|---|---|---|
| Trivial (typos, simple fixes) | 2-5 minutes | 1-2 |
| Minor (UI glitches, edge cases) | 5-15 minutes | 2-5 |
| Moderate (logic errors, data flow) | 15-45 minutes | 5-10 |
| Major (broken features, crashes) | 45 min - 2 hours | 10-20 |
Prerequisites
Before starting bug fixing, ensure:
1. Bug Report Available
Create or locate a bug report with:
## Bug Report
### Title
[Clear, concise bug description]
### Severity
[Critical / High / Medium / Low / Trivial]
### Environment
- Browser/OS: Chrome 121 / macOS
- User actions: Steps to reproduce
- Frequency: Always / Sometimes / Once
### Steps to Reproduce
1. Go to /dashboard
2. Click "New Todo" button
3. Enter todo text
4. Press Enter
5. Bug occurs
### Expected Behavior
Todo should be added to list
### Actual Behavior
Todo not visible, console error shown
### Error Messages
TypeError: Cannot read property 'map' of undefined
### Screenshots/Videos
[Attach if applicable]2. Codebase Accessible
# Repository available
git status
# Dependencies installed
npm install
Can run tests
npm test
Can run dev server
npm run dev3. agentful Initialized
# agentful ready
ls .claude/agents/
ls .agentful/The Bug Fixing Loop
Complete Workflow Diagram
┌─────────────────────────────────────────────────────────────┐
│ START: Bug Report Received │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 1. BUG REPORTING │
│ • Document bug details │
│ • Severity assessment │
│ • Environment information │
│ • Steps to reproduce │
│ • Error messages/stack traces │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. BUG REPRODUCTION │
│ • Delegate to appropriate agent (@frontend or @backend) │
│ • Follow reproduction steps │
│ • Verify bug exists │
│ • Capture error details │
│ • Identify affected files │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. ROOT CAUSE ANALYSIS │
│ • Inspect stack traces │
│ • Trace data flow │
│ • Check recent changes (git log) │
│ • Identify exact line causing issue │
│ • Understand why bug occurs │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. FIX IMPLEMENTATION │
│ • Delegate to specialist agent │
│ • Implement minimal fix │
│ • Don't refactor (separate workflow) │
│ • Add inline comment explaining fix │
│ • Ensure no side effects │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. REGRESSION TESTING │
│ • Delegate to @tester agent │
│ • Write test that reproduces bug │
│ • Verify test fails before fix │
│ • Verify test passes after fix │
│ • Add related edge case tests │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. VALIDATION │
│ • Delegate to @reviewer agent │
│ • Run all quality checks │
│ • Ensure no regressions │
│ • Verify fix doesn't break other features │
└─────────────────────────────────────────────────────────────┘
↓
Issues found?
│
┌──────────┴──────────┐
↓ ↓
┌─────────────────────────┐ ┌─────────────────────────┐
│ 7a. FIX ITERATION(S) │ │ 7b. BUG FIXED │
│ • @fixer resolves │ │ • All checks pass │
│ • Re-validate │ │ • Update progress │
│ • Loop until pass │ │ • Mark bug resolved │
└─────────────────────────┘ └─────────────────────────┘
│ │
└──────────┬──────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 8. DOCUMENTATION │
│ • Update .agentful/completion.json │
│ • Add bug to "bugs-resolved" count │
│ • Document fix in commit message │
│ • Update related documentation if needed │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 9. NEXT BUG / FEATURE │
│ • Loop back to bug queue or feature queue │
│ • Continue until all bugs resolved │
└─────────────────────────────────────────────────────────────┘Step-by-Step Guide
Step 1: Report the Bug
Input: Bug discovery or user report
Bug Report Template:## Bug: Navigation broken on mobile
**Severity:** High
**Priority:** P1
**Environment:**
- Device: iPhone 12
- Browser: Safari 16
- Screen size: 390x844
- App version: 1.2.0
**Steps to Reproduce:**
1. Open app on mobile device
2. Navigate to Settings page
3. Tap "Notifications" menu item
4. Navigation menu remains open
**Expected Behavior:**
Navigation menu should close after selection
**Actual Behavior:**
Menu stays open, overlaying content
**Console Output:**Warning: setState(...): Can only update a mounted or mounting component.
**Frequency:**
Always (100% reproducible)
**Impact:**
Users cannot navigate effectively on mobile devices
**Workaround:**
Manually close menu by tapping outside# Create bug report
cat > .agentful/bugs/mobile-navigation.md << 'EOF'
[Paste bug report template above]
EOFStep 2: Reproduce the Bug
Agent: @frontend (for UI bugs) or @backend (for API bugs) Time: 2-10 minutes
Agent actions:// Frontend Agent reproducing mobile navigation bug
// 1. Start dev server
// npm run dev
// 2. Access on mobile viewport (390x844)
// 3. Navigate to /settings
// 4. Click "Notifications"
// 5. Observe: Menu still open
// 6. Check browser console
// Warning: setState(...): Can only update a mounted or mounting component.
// 7. Identify affected files
// - src/components/Navigation.tsx
// - src/components/MobileMenu.tsx
// 8. Inspect the code
// src/components/MobileMenu.tsx
export function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const handleItemClick = (item: string) => {
router.push(item); // Navigation happens
setIsOpen(false); // ❌ Bug: Component might unmount during navigation
};
return (
<Menu isOpen={isOpen}>
{items.map(item => (
<MenuItem onClick={() => handleItemClick(item.path)}>
{item.label}
</MenuItem>
))}
</Menu>
);
}🔍 Frontend Agent reproducing bug...
Reproducing steps:
✓ Opened dev server (http://localhost:3000)
✓ Set viewport to 390x844 (mobile)
✓ Navigated to /settings
✓ Clicked "Notifications" menu item
Bug reproduced:
✅ Menu remains open after navigation
✅ Console warning: setState(...) on unmounted component
✅ Overlay blocks content interaction
Affected files identified:
• src/components/MobileMenu.tsx (line 15)
• src/components/Navigation.tsx (line 8)
Root cause hypothesis:
setState called after component unmounts during navigation
Time: 4 minutesStep 3: Root Cause Analysis
Agent: @frontend or @backend Time: 3-10 minutes
Agent analysis:// Root cause analysis
// 1. Check component lifecycle
export function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const mountedRef = useRef(true); // ❌ Missing mount tracking
const handleItemClick = (item: string) => {
router.push(item); // This triggers unmount
setTimeout(() => { // ❌ Delayed setState
setIsOpen(false); // Component might be unmounted here
}, 100);
};
return <Menu>...</Menu>;
}
// 2. Why does this happen?
// - router.push() triggers navigation
// - Navigation causes component unmount
// - setTimeout delays setIsOpen
// - setIsOpen runs after unmount → Warning
// 3. Check git history for recent changes
// $ git log --oneline -10
// abc1234 feat: add mobile navigation (2 days ago)
// def5678 fix: menu animation timing
// 4. The bug was introduced in abc1234
// 5. Exact line causing issue:
// src/components/MobileMenu.tsx:15
// setIsOpen(false) called after navigation🔬 Root Cause Analysis
ROOT CAUSE:
setState called after component unmounts during navigation
AFFECTED CODE:
src/components/MobileMenu.tsx, line 15
EXPLANATION:
1. User clicks menu item
2. router.push() triggers navigation (line 14)
3. Navigation causes MobileMenu component to unmount
4. setTimeout fires (line 15)
5. setIsOpen(false) called on unmounted component
6. React warns about setState on unmounted component
WHY IT HAPPENS:
Navigation is synchronous, setState is delayed by setTimeout
Component unmounts before setState executes
INTRODUCED IN:
Commit abc1234 (2 days ago)
"feat: add mobile navigation"
IMPACT:
Mobile users cannot navigate properly
Menu stays open blocking content
Time: 6 minutesStep 4: Implement Fix
Agent: @frontend Time: 2-5 minutes
Fix implementation:// BEFORE (buggy code)
export function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const handleItemClick = (item: string) => {
router.push(item);
setTimeout(() => {
setIsOpen(false); // ❌ Bug: setState after unmount
}, 100);
};
return <Menu>...</Menu>;
}
// AFTER (fixed code)
export function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const handleItemClick = (item: string) => {
setIsOpen(false); // ✅ Fix: Close menu BEFORE navigation
router.push(item); // ✅ Navigation happens after menu closes
};
return <Menu>...</Menu>;
}
// Alternative fix (if animation needed):
export function MobileMenu() {
const [isOpen, setIsOpen] = useState(false);
const isMountedRef = useRef(true);
useEffect(() => {
return () => {
isMountedRef.current = false; // Track unmount
};
}, []);
const handleItemClick = (item: string) => {
router.push(item);
setTimeout(() => {
if (isMountedRef.current) { // ✅ Check if still mounted
setIsOpen(false);
}
}, 100);
};
return <Menu>...</Menu>;
}- ✅ Fix the bug only
- ❌ Don't refactor (use separate workflow)
- ✅ Add comment explaining fix
- ✅ No side effects
🔧 Frontend Agent implementing fix...
Modified:
✓ src/components/MobileMenu.tsx
Changes:
- Moved setIsOpen(false) before router.push()
- Added comment explaining fix
- Removed unnecessary setTimeout
Diff:
- const handleItemClick = (item: string) => {
- router.push(item);
- setTimeout(() => {
- setIsOpen(false);
- }, 100);
- };
+ const handleItemClick = (item: string) => {
+ setIsOpen(false); // Close menu before navigation
+ router.push(item);
+ };
Fix applied successfully!
Time: 2 minutesStep 5: Regression Testing
Agent: @tester Time: 5-15 minutes
Test implementation:// 1. Test that reproduces bug
// src/components/__tests__/MobileMenu.test.tsx
describe('MobileMenu', () => {
it('should close menu after navigation', async () => {
const mockRouter = {
push: vi.fn(),
};
render(<MobileMenu />);
// Open menu
fireEvent.click(screen.getByText('Menu'));
expect(screen.getByRole('menu')).toBeVisible();
// Click menu item
fireEvent.click(screen.getByText('Notifications'));
// Verify menu closed
await waitFor(() => {
expect(screen.queryByRole('menu')).not.toBeVisible();
});
// Verify navigation occurred
expect(mockRouter.push).toHaveBeenCalledWith('/settings/notifications');
});
it('should not cause setState warning on unmount', async () => {
const mockRouter = {
push: vi.fn(),
};
const { unmount } = render(<MobileMenu />);
// Open menu
fireEvent.click(screen.getByText('Menu'));
// Click item that causes navigation/unmount
fireEvent.click(screen.getByText('Notifications'));
// Unmount manually (simulating navigation)
unmount();
// Should not throw setState warning
// Test will fail if setState called after unmount
expect(() => {
// Small delay to catch any delayed setState calls
vi.advanceTimersByTime(200);
}).not.toThrow();
});
it('should handle rapid menu item clicks', async () => {
const mockRouter = {
push: vi.fn(),
};
render(<MobileMenu />);
// Open menu
fireEvent.click(screen.getByText('Menu'));
// Rapidly click multiple items
fireEvent.click(screen.getByText('Notifications'));
fireEvent.click(screen.getByText('Profile'));
fireEvent.click(screen.getByText('Settings'));
// Should only navigate once (last click)
expect(mockRouter.push).toHaveBeenCalledTimes(1);
expect(mockRouter.push).toHaveBeenCalledWith('/settings');
// Menu should be closed
expect(screen.queryByRole('menu')).not.toBeVisible();
});
});
// 2. Integration test for full navigation flow
// e2e/mobile-navigation.spec.ts
test.describe('Mobile Navigation', () => {
test('should close menu after navigation', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 390, height: 844 });
// Navigate to settings
await page.goto('/settings');
// Open menu
await page.click('[aria-label="Menu"]');
await expect(page.locator('[role="menu"]')).toBeVisible();
// Click menu item
await page.click('text=Notifications');
// Verify menu closed
await expect(page.locator('[role="menu"]')).not.toBeVisible();
// Verify navigation occurred
await expect(page).toHaveURL('/settings/notifications');
// Verify no console errors
const errors = [];
page.on('console', msg => {
if (msg.type() === 'error') errors.push(msg.text());
});
expect(errors.length).toBe(0);
});
});🧪 Tester Agent writing regression tests...
Created:
✓ src/components/__tests__/MobileMenu.test.tsx (3 tests)
✓ e2e/mobile-navigation.spec.ts (1 test)
Running tests...
Before fix:
✗ should close menu after navigation (FAILED)
Expected menu to close, but it remained open
✗ should not cause setState warning (FAILED)
setState warning detected in console
After fix:
✓ should close menu after navigation (PASSED)
✓ should not cause setState warning (PASSED)
✓ should handle rapid menu item clicks (PASSED)
Regression tests added: 4
Coverage: +12% for MobileMenu component
Time: 8 minutesStep 6: Validation
Agent: @reviewer Time: 1-2 minutes
Validation checks:# 1. TypeScript
npx tsc --noEmit
✓ No type errors
# 2. Lint
npm run lint
✓ No lint errors
# 3. Dead Code
npx knip
✓ No dead code
# 4. Tests
npm test
✓ All tests passing (including new regression tests)
# 5. Coverage
npm test -- --coverage
✓ Coverage maintained (no decrease)
# 6. Security
npm audit --production
✓ No vulnerabilities
# 7. Regression check
# Verify fix doesn't break other navigation
npm test -- --testNamePattern="Navigation"
✓ All navigation tests still passing🔍 Reviewer Agent validating fix...
Quality Gates:
✅ TypeScript: No errors (0 issues)
✅ Lint: No errors (0 issues)
✅ Dead Code: None (0 issues)
✅ Tests: All passing (47/47)
✅ Coverage: 84% (maintained)
✅ Security: No issues (0 vulnerabilities)
✅ Regression: No broken features
Overall: ✅ PASSED
Bug fix validated!
Time: 45 secondsStep 7: Update Completion
Agent: @orchestrator Time: Instant
State update:// .agentful/completion.json
{
"bugs_resolved": {
"mobile-navigation": {
"id": "bug-001",
"title": "Navigation broken on mobile",
"severity": "High",
"status": "resolved",
"fixed_at": "2026-01-18T01:30:00Z",
"files_modified": [
"src/components/MobileMenu.tsx"
],
"tests_added": 4,
"commit_hash": "xyz789"
}
},
"bugs_active": []
}
// .agentful/state.json
{
"current_task": null,
"current_phase": "idle",
"iterations": 3,
"bugs_fixed_this_session": 1
}git add .
git commit -m "fix: close mobile menu before navigation
Fixes bug where mobile navigation menu remained open after
menu item selection, blocking content interaction.
Root cause:
setState called after component unmounts during navigation.
Solution:
Close menu synchronously before triggering navigation.
Tests:
- Added 3 unit tests for menu close behavior
- Added 1 E2E test for mobile navigation flow
- Verified no setState warnings in console
Fixes: #123✅ Bug Fixed: Mobile Navigation
Severity: High
Time to fix: 22 minutes
Iterations: 3
Files modified: 1
Tests added: 4
Coverage maintained: 84%
Quality gates: All passing
Regressions: None detected
Bug resolved and committed!
Commit: xyz789Real Example: Debugging API Error
Bug Report
## Bug: User profile returns 500 error
**Severity:** High
**Steps to Reproduce:**
1. Login as user
2. Navigate to /profile
3. Page shows error message
**Expected:**
Profile page displays user information
**Actual:**
"Internal server error" message
**Console:**
GET /api/user/profile 500Reproduction
Agent: @backend
// Backend Agent reproducing bug
// 1. Start dev server
// npm run dev
// 2. Make API request
// GET /api/user/profile
// 3. Observe error
// Error: Cannot read property 'email' of undefined
// 4. Check server logs
// TypeError: Cannot read property 'email' of undefined
// at src/services/user.service.ts:15
// 5. Inspect the code
// src/app/api/user/profile/route.ts
export async function GET(req: NextRequest) {
const session = await getSession(req); // Returns null if not authenticated
const service = new UserService();
const profile = await service.getProfile(session.user.id); // ❌ session is null
// ↑
// src/services/user.service.ts Error here
export class UserService {
async getProfile(userId: string) {
return this.userRepository.findById(userId);
}
}ROOT CAUSE:
getSession() returns null, code doesn't handle this case
AFFECTED CODE:
src/app/api/user/profile/route.ts:8
EXPLANATION:
1. User session expires or is invalid
2. getSession() returns null
3. Code tries to access null.user.id
4. TypeError: Cannot read property 'email' of undefined
FIX NEEDED:
Add null check for session before accessing user dataFix Implementation
// BEFORE (buggy)
export async function GET(req: NextRequest) {
const session = await getSession(req);
const service = new UserService();
const profile = await service.getProfile(session.user.id); // ❌ Crashes
return NextResponse.json(profile);
}
// AFTER (fixed)
export async function GET(req: NextRequest) {
const session = await getSession(req);
// ✅ Handle null session
if (!session || !session.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const service = new UserService();
const profile = await service.getProfile(session.user.id);
return NextResponse.json(profile);
}Regression Tests
// src/app/api/user/__tests__/profile.test.ts
describe('GET /api/user/profile', () => {
it('should return user profile for authenticated user', async () => {
const session = { user: { id: '123', email: 'test@example.com' } };
getSession.mockResolvedValue(session);
const res = await request(app).get('/api/user/profile');
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('email', 'test@example.com');
});
it('should return 401 when session is null', async () => {
getSession.mockResolvedValue(null); // Simulate expired session
const res = await request(app).get('/api/user/profile');
expect(res.status).toBe(401);
expect(res.body).toHaveProperty('error', 'Unauthorized');
});
it('should return 401 when session.user is missing', async () => {
const session = {}; // Session exists but no user
getSession.mockResolvedValue(session);
const res = await request(app).get('/api/user/profile');
expect(res.status).toBe(401);
expect(res.body).toHaveProperty('error', 'Unauthorized');
});
});Result
✅ Bug Fixed: User Profile API Error
Files modified: 1
Tests added: 3
Time: 18 minutes
Validation: All gates passing
Regressions: NoneHandling Edge Cases
Case 1: Bug Can't Be Reproduced
🔍 Attempting to reproduce bug...
Following steps:
1. Go to /dashboard ✓
2. Click "Export" ✓
3. Wait for download...
Result: ✗ Bug NOT reproduced
Download worked correctly
Possible reasons:
- Environment-specific (browser, OS, device)
- Timing/race condition
- Already fixed in latest version
- Incomplete reproduction steps
Action:
Added to decisions.json for user input:
"Unable to reproduce bug. Please provide:
- Browser version
- Console errors
- Network tab errors
- Screenshots/video"
Run /agentful-decide to provide more information.Case 2: Multiple Root Causes
🔬 Root cause analysis...
Multiple potential causes identified:
1. Race condition in data fetching (src/hooks/useData.ts:23)
2. Missing error boundary in component tree
3. API returning inconsistent data types
Recommended approach:
Fix most likely cause first (race condition)
Add regression tests
Validate
If bug persists, fix next cause
Delegating to @frontend to fix race condition...Case 3: Fix Causes New Bugs
🔍 Validating fix...
❌ VALIDATION FAILED
New issues found:
1. Test: mobile-menu-spacing (FAILED)
Menu now closes too quickly, animation looks jerky
2. Test: rapid-navigation (FAILED)
Second navigation not working
Action: Reverting fix, using alternative approach
🔧 Implementing alternative fix...
Using isMountedRef pattern instead
Preserves animation while preventing setState error
✅ Validation passedBest Practices
1. Create Detailed Bug Reports
Good bug reports speed up fixing:
Good:## Steps to Reproduce
1. Go to /dashboard
2. Click "Add Widget" button
3. Select "Weather" widget
4. Click "Add"
5. Widget not visible
## Expected
Weather widget appears on dashboard
## Actual
Widget not visible, console error:
"TypeError: widget.type is undefined"
## Environment
Chrome 121, macOS 14, screen size 1920x1080## Bug
Dashboard not working
## What I did
Clicked some buttons
## What happened
Doesn't work2. Fix Root Cause, Not Symptoms
Symptom fix (bad):// Suppress the error
try {
setIsOpen(false);
} catch (e) {
// Ignore error
}// Fix the actual problem
setIsOpen(false); // Before navigation
router.push(item);3. Add Regression Tests
Always add tests that:
- Reproduce the bug
- Verify the fix
- Prevent recurrence
it('should [description of bug]', () => {
// Arrange: Setup conditions that caused bug
// Act: Perform actions that triggered bug
// Assert: Verify bug is fixed
});4. Don't Refactor While Fixing
Fixing bugs ≠ Refactoring
Fix bugs:- Minimal change
- Addresses specific issue
- No unrelated changes
- Use refactoring workflow
- Comprehensive changes
- Improves overall structure
5. Document the Fix
Add clear comments:
// Fix: Close menu before navigation to prevent setState on unmounted component
// Bug: Menu remained open after navigation, blocking content
// Solution: Synchronous menu close prior to router.push()
const handleItemClick = (item: string) => {
setIsOpen(false);
router.push(item);
};Monitoring Bug Fixes
Track Fixed Bugs
# See all resolved bugs
cat .agentful/completion.json | grep bugs_resolved
# Check recent commits
git log --oneline --grep="fix" -10Bug Metrics
Track in completion.json:
{
"bugs_resolved": {
"this_session": 3,
"this_week": 12,
"total": 47
},
"bug_severity_breakdown": {
"critical": 2,
"high": 15,
"medium": 20,
"low": 10
},
"average_fix_time": {
"critical": "45 minutes",
"high": "25 minutes",
"medium": "15 minutes",
"low": "8 minutes"
}
}Next Steps
Feature Development
After fixing bugs, continue building features → Feature Development Guide
Refactoring
Improve code quality with refactoring workflow → Refactoring Guide
Quick Reference
Report Bug:# Create bug report
cat > .agentful/bugs/bug-name.md << 'EOF'
[Paste bug report template]
EOF/agentful-start
# Automatically picks up bug from bugs/ directory/agentful-status
# Shows active and resolved bugs/agentful-validate
# Ensures no regressions