TUTORIAL 04

Fix a Bug

Reproduce, isolate, fix, and verify a bug using test-driven debugging

โฑ ~15 min ยท Hands-on

What You'll Learn

You'll fix a real bug reported by QA โ€” email validation rejecting valid plus signs โ€” using the test-first debugging approach. Write a failing test that reproduces the bug, find the root cause, fix it, and verify no regressions.

/agile-code-branch /agile-code-tdd /agile-code-debug /agile-code-ci /agile-code-commit /agile-code-pr /agile-code-pr-review /agile-code-merge
Prerequisites

The Scenario

QA has filed a bug report:

BUG-042: Login fails with plus sign in email

Steps to reproduce: Try to register or log in with an email like user+tag@example.com.

Expected: Login succeeds (plus signs are valid in email local parts per RFC 5321).

Actual: 400 error โ€” "Invalid email format".

Impact: Users who use email aliases (Gmail plus addressing) cannot use the application.

Step 1: Understand the Bug

Before touching code, restate the bug in precise technical terms:

Bug: Email validation regex rejects the "+" character in the local part of email addresses.

Root cause hypothesis: The regex pattern used for email validation
does not include "+" in its character class for the local part.

Affected component: src/validators/email-validator.js (or equivalent)
Always restate before fixing

Restating the bug in your own words forces you to understand it. A vague understanding leads to a vague fix. "Login is broken" could mean anything. "Regex rejects valid + character" tells you exactly where to look and what to fix.

Step 2: Create a Fix Branch with /agile-code-branch

Before writing any code, create a branch for the bug fix:

/agile-code-branch
๐ŸŒฟ Branch created: fix/BUG-042-email-plus-sign

Source: main
Convention: fix/[BUG-ID]-[short-description]
Bug link: BUG-042 โ€” Login fails with plus sign in email

Switched to branch 'fix/BUG-042-email-plus-sign'

Step 3: Reproduce with a Failing Test โ€” /agile-code-tdd (RED)

The first thing you do with any bug is prove it exists with a test:

/agile-code-tdd
๐Ÿ”ด RED โ€” Writing test to reproduce bug BUG-042...

// test/validators/email-validator.test.js
describe('EmailValidator', () => {
  describe('plus sign handling', () => {
    it('should_accept_email_with_plus_sign', () => {
      const result = EmailValidator.isValid('user+tag@example.com');
      expect(result).toBe(true);
    });
  });
});

Running test...
  โœ— should_accept_email_with_plus_sign โ€” FAILED
    Expected: true
    Received: false

1 failing โ† Bug reproduced! The test proves the defect exists.
Reproduction = proof

A failing test that reproduces the bug is the most valuable artifact in bug fixing. It proves the bug exists, it defines exactly what "fixed" means, and it prevents the bug from ever coming back (regression).

Step 4: Investigate the Root Cause

Now look at the actual validation code:

// src/validators/email-validator.js (current โ€” buggy)

class EmailValidator {
  static EMAIL_REGEX = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

  static isValid(email) {
    return this.EMAIL_REGEX.test(email);
  }
}

Found it. The character class [a-zA-Z0-9._-] allows dots, underscores, and hyphens in the local part โ€” but not plus signs. The + character is missing from the regex.

Why this happened

Email validation is notoriously tricky. The original developer covered the most common characters but missed that + is valid per RFC 5321. This is why tests against edge cases matter โ€” they catch what human intuition misses.

Step 5: Fix the Bug

Add the + character to the regex character class:

// src/validators/email-validator.js (fixed)

class EmailValidator {
  static EMAIL_REGEX = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

  static isValid(email) {
    return this.EMAIL_REGEX.test(email);
  }
}

The only change: [a-zA-Z0-9._-] became [a-zA-Z0-9._+-] โ€” adding + to the allowed characters.

Step 6: Verify the Fix (GREEN)

๐ŸŸข GREEN โ€” Running reproduction test...

  โœ“ should_accept_email_with_plus_sign โ€” PASSED

1 passing โ† Bug is fixed!
The test flipped from red to green

The same test that proved the bug exists now proves it's fixed. No ambiguity, no manual testing needed. This test will run in CI forever, ensuring this bug never returns.

Step 7: Add Edge Case Tests

One fix is good, but comprehensive coverage is better. Add tests for related edge cases:

// Additional edge case tests
describe('EmailValidator โ€” edge cases', () => {
  it('should_accept_email_with_dots', () => {
    expect(EmailValidator.isValid('first.last@example.com')).toBe(true);
  });

  it('should_accept_email_with_hyphens', () => {
    expect(EmailValidator.isValid('first-last@example.com')).toBe(true);
  });

  it('should_accept_email_with_multiple_plus_signs', () => {
    expect(EmailValidator.isValid('user+tag+extra@example.com')).toBe(true);
  });

  it('should_reject_email_without_at_sign', () => {
    expect(EmailValidator.isValid('userexample.com')).toBe(false);
  });

  it('should_reject_email_without_domain', () => {
    expect(EmailValidator.isValid('user@')).toBe(false);
  });
});

Running all email validation tests...
  โœ“ should_accept_email_with_plus_sign โ€” PASSED
  โœ“ should_accept_email_with_dots โ€” PASSED
  โœ“ should_accept_email_with_hyphens โ€” PASSED
  โœ“ should_accept_email_with_multiple_plus_signs โ€” PASSED
  โœ“ should_reject_email_without_at_sign โ€” PASSED
  โœ“ should_reject_email_without_domain โ€” PASSED

6 passing โ€” Full edge case coverage.
Expand coverage around the bug

When you fix a bug, always add tests for nearby edge cases. If + was missing, what else might be? Test dots, hyphens, multiple specials, and invalid formats. This hardens the area around the fix.

Step 8: Run CI Checks with /agile-code-ci

Verify no regressions across the entire test suite:

/agile-code-ci
๐Ÿ”„ Running CI pipeline locally...

Build:
  โœ… Compilation successful

Tests:
  โœ… 48 tests passing (was 42 โ€” added 6 edge case tests)
  โœ… Coverage: 96% lines
  โœ… 0 regressions โ€” all existing tests still pass

Lint:
  โœ… ESLint: 0 errors
  โœ… Prettier: all formatted

Security:
  โœ… No vulnerabilities

โœ… All CI checks pass. No regressions detected.
Zero regressions

The fix changed one character in a regex โ€” but CI verified that all 48 tests pass, including the 42 that existed before. This confidence is what lets you ship a bug fix quickly without fear.

Step 9: Commit the Fix with /agile-code-commit

/agile-code-commit
๐Ÿ“ฆ Commit created:

fix(auth): accept plus signs in email validation

The email regex was missing "+" from the local part character class,
causing emails like user+tag@example.com to be rejected. Added "+"
to the regex and 6 edge case tests for comprehensive coverage.

Fixes: BUG-042

Step 10: Create PR with /agile-code-pr

/agile-code-pr
๐Ÿ”€ Pull Request created: #22

Title: fix(auth): accept plus signs in email validation
Branch: fix/BUG-042-email-plus-sign โ†’ main
Bug: BUG-042

Changes:
  ~1 file modified: src/validators/email-validator.js (1 character added)
  +1 file modified: test/validators/email-validator.test.js (6 tests added)

Test coverage: 96%
CI status: โœ… All checks passing

Step 11: Code Review with /agile-code-pr-review

/agile-code-pr-review
๐Ÿ‘€ Code Review โ€” PR #22
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Findings:
  โœ… Fix is minimal and correct โ€” only the necessary character added
  โœ… No over-engineering (didn't rewrite the entire validator)
  โœ… Edge case tests are thorough
  โœ… Commit message follows conventions with Fixes: BUG-042

Notes:
  @lead: "Clean fix. Consider switching to a battle-tested email
  validation library (e.g., validator.js) in a future sprint to
  avoid maintaining our own regex. Not blocking this PR."

Verdict: โœ… APPROVED by @lead

Step 12: Merge with /agile-code-merge

With the PR approved, merge the fix branch into main:

/agile-code-merge
๐Ÿ”€ Merge โ€” PR #22
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Pre-merge checks:
  โœ… PR approved by @lead
  โœ… CI passing on branch
  โœ… No merge conflicts
  โœ… Branch up to date with main

Merging: fix/BUG-042-email-plus-sign โ†’ main
Strategy: squash merge (project convention)

Merge complete.
Branch fix/BUG-042-email-plus-sign deleted (was 9c4e1a2).

โœ… PR #22 merged to main.

When You Can't Reproduce the Bug

Use /agile-code-debug for tricky bugs

If writing a failing test doesn't reproduce the bug (e.g., it only happens in production, under load, or with specific data), use /agile-code-debug for systematic investigation:

/agile-code-debug
๐Ÿ” Debug Mode โ€” Systematic Investigation
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Hypothesis 1: Race condition under concurrent requests
  Test: Run 100 concurrent login requests with valid emails
  Result: All pass โ† Not a race condition

Hypothesis 2: Character encoding issue in production
  Test: Send email with UTF-8 encoding header vs ASCII
  Result: Both pass locally โ† Might be environment-specific

Hypothesis 3: Database collation rejecting special characters
  Test: Direct DB query with plus sign email
  Result: โœ— Query returns empty! โ† Found it!
  Root cause: DB column uses C collation, not UTF-8

Attempts: 3/5 (max 5 before escalation)
Status: Root cause identified
Bounded debugging

The /agile-code-debug command forms hypotheses and tests them one at a time, with a maximum of 5 attempts. If all 5 fail, it escalates with a diagnostic capture instead of spinning endlessly. This prevents the "I've been debugging for 3 hours" trap.

The Bug Fix Flow

๐Ÿ›
Understand Restate the bug precisely
๐Ÿงช
Reproduce /agile-code-tdd (RED)
๐Ÿ”
Find Cause Read the code
๐Ÿ”ง
Fix Minimal change
โœ…
Verify /agile-code-ci + PR

Knowledge Check

What's the first thing to do when fixing a bug?

What You Learned