C++ Systematic Debugger
An interactive workflow for methodically debugging C++ issues through hypothesis elimination, test-driven investigation, and documented reasoning.
Workflow Overview
1. PARSE TICKET → Read DEBUG_TICKET.md for issue details
2. INVESTIGATE → Review code, headers, and existing tests
3. ELIMINATE → Rule out causes with evidence
4. PROPOSE → Suggest diagnostic tests
5. IMPLEMENT → Write and run tests (GTest/Catch2)
6. CHECKPOINT → Get human feedback, iterate or resolve
Step 0: Debug Ticket
Before beginning, look for DEBUG_TICKET.md in the project root or current directory. This file contains the structured problem description from the user.
If no ticket exists, ask the user to create one using the template in assets/DEBUGTICKETTEMPLATE.md.
Required ticket sections:
- Problem description and symptoms
- Suspect files/classes/functions
- Build environment and compiler info
- Reproduction steps
Step 1: Initialize Debug Session
Create a debug log file to track the investigation:
mkdir -p .debug-sessions
SESSION_ID=$(date +%Y%m%d_%H%M%S)
cp DEBUG_TICKET.md .debug-sessions/debug_${SESSION_ID}.md
Append investigation sections to the log. See references/debug-log-template.md for the full format.
1.1 Create or Resume Iteration Log
In addition to the debug session log, create or resume an iteration log for tracking build-test cycles:
- Location:
docs/investigations/{feature-name}/iteration-log.md
- Template:
.claude/templates/iteration-log.md.template
If the log already exists (from a previous session), read it fully before making any changes. This is critical for avoiding repeated approaches.
Relationship to debug log: The debug log tracks hypotheses and reasoning. The iteration log tracks build-test cycle results and is used for circle detection. Both are maintained in parallel.
Step 2: Investigate Suspect Code
For each file/class/function identified in the ticket:
2.1 Header and Documentation Review
- Read header files (.h/.hpp) for class declarations and API contracts
- Check Doxygen comments and inline documentation
- Look for
@pre, @post, @throws annotations
- Note any
static_assert, concept, or SFINAE constraints
- Review
#define guards and macro usage
2.2 Implementation Analysis
- Trace data flow through suspect .cpp files
- Identify memory management patterns (RAII, raw pointers, smart pointers)
- Check for common C++ pitfalls:
- Use-after-free / dangling references - Iterator invalidation - Object slicing - Undefined behavior (signed overflow, null deref, uninitialized reads) - Thread safety issues (data races, deadlocks)
- Review exception safety guarantees
- Check move/copy semantics
2.3 Test Coverage Analysis
# Find existing tests
find . -name "*test*.cpp" -o -name "*_test.cpp" -o -name "test_*.cpp" | head -20
# Check for test framework
grep -r "gtest\|catch\|doctest\|boost.test" --include="CMakeLists.txt" .
# If using gcov/lcov:
# lcov --capture --directory . --output-file coverage.info
# lcov --list coverage.info | grep "suspect_file"
Document in debug log:
- Class hierarchy and ownership model
- Thread safety assumptions
- Existing test coverage (or gaps)
- Initial observations related to the issue
Step 3: Systematic Elimination
For each potential cause, document:
### Hypothesis: [Brief description]
**Status:** ⏳ Investigating | ✅ Eliminated | ❌ Confirmed as cause | 🔄 Needs more info
**Evidence gathered:**
- [What was checked]
- [What was found]
**Conclusion:** [Why this is/isn't the cause]
Elimination methods:
- Code inspection - Logic errors, off-by-one, null checks
- Test verification - Does existing test cover this case?
- Log analysis - Add logging, review output
- Reproduction - Minimal test case that triggers issue
- Bisection - Narrow down to specific commit/change
Step 4: Propose Diagnostic Tests
Based on investigation, propose tests that will:
- Isolate the issue - Test suspect component in isolation
- Verify assumptions - Test preconditions and postconditions
- Cover edge cases - Test boundaries, nulls, empty inputs
- Reproduce the bug - Test that currently fails, will pass when fixed
Test proposal format:
### Proposed Test: [Name]
**Purpose:** What this test will prove or disprove
**Target:** Which component/function/behavior
**Type:** Unit | Integration | Regression
**Expected outcome:** What we expect to learn
**Test outline:**
- Setup: [preconditions]
- Action: [what to test]
- Assert: [expected results]
Present proposals to user before implementing.
Step 5: Implement Diagnostic Tests
After user approval, implement tests using the project's test framework.
5.1 Test Framework Detection
# Check CMakeLists.txt for test framework
grep -E "gtest|GTest|catch|Catch2|doctest|Boost.*Test" CMakeLists.txt
# Common locations
ls tests/ test/ unittest/ 2>/dev/null
5.2 GTest Implementation Pattern
// tests/debug_investigation_test.cpp
#include <gtest/gtest.h>
#include "suspect_class.h"
// Link to hypothesis H1 in debug log
TEST(DebugInvestigation, H1_BoundaryCondition) {
SuspectClass obj;
// Test the specific condition from hypothesis
EXPECT_EQ(obj.method(edge_case_input), expected_output);
}
TEST(DebugInvestigation, H2_NullHandling) {
SuspectClass obj;
EXPECT_NO_THROW(obj.method(nullptr));
}
5.3 Catch2 Implementation Pattern
// tests/debug_investigation_test.cpp
#include <catch2/catch_test_macros.hpp>
#include "suspect_class.h"
TEST_CASE("Debug H1: Boundary condition", "[debug][h1]") {
SuspectClass obj;
REQUIRE(obj.method(edge_case_input) == expected_output);
}
TEST_CASE("Debug H2: Null handling", "[debug][h2]") {
SuspectClass obj;
REQUIRE_NOTHROW(obj.method(nullptr));
}
5.4 Build and Run Tests
# CMake build
mkdir -p build && cd build
cmake .. -DBUILD_TESTING=ON
cmake --build . --target debug_investigation_test
# Run specific test
./debug_investigation_test --gtest_filter="DebugInvestigation.*"
# or for Catch2:
./debug_investigation_test "[debug]"
5.5 Run with Sanitizers (if not already enabled)
# Rebuild with sanitizers for deeper investigation
cmake .. -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g"
cmake --build .
./debug_investigation_test
5.6 Document Results
Update debug log with:
- Test code location
- Build configuration used
- Execution results (including sanitizer output)
- What was learned
- Updated hypothesis status
Step 5.7: Iteration Tracking and Auto-Commit
After each build+test cycle (whether diagnostic tests pass or fail), follow this protocol:
1. Record Iteration Entry
Append a new entry to docs/investigations/{feature-name}/iteration-log.md:
### Iteration N — {YYYY-MM-DD HH:MM}
**Commit**: {short SHA}
**Hypothesis**: {Why this change was made — what problem it's solving}
**Changes**:
- `path/to/file.cpp`: {description of change}
**Build Result**: PASS / FAIL ({details if fail})
**Test Result**: {pass}/{total} — {list of new failures or fixes vs previous iteration}
**Impact vs Previous**: {+N passes, -N regressions, net change}
**Assessment**: {Does this move us forward? Any unexpected side effects?}
2. Auto-Commit
After each successful build+test cycle, commit all changes:
git add {changed files} {iteration-log.md}
git commit -m "investigate: iteration {N} — {one-line summary}
{ticket-name}
Co-Authored-By: Claude Opus 4.6 <[email protected]>"
3. Circle Detection — Before Next Change
Before making the next change, read the iteration log and check for:
- Repetition: Same file modified 3+ times with similar changes
- Oscillation: Test results alternating between iterations (fixes A/breaks B, then fixes B/breaks A)
- Recycled hypothesis: Same hypothesis attempted with same approach
If a circle is detected:
- STOP making changes
- Document the pattern in the iteration log under "Circle Detection Flags"
- Escalate to the human at the next checkpoint (Step 6) with a summary of what has been tried
Step 6: Human Checkpoint
At each checkpoint, present:
## Debug Session Checkpoint
### Progress Summary
- Hypotheses eliminated: [count]
- Hypotheses confirmed: [count]
- Hypotheses pending: [count]
### Key Findings
[Most important discoveries]
### Current Best Theory
[What we think is happening and why]
### Recommended Next Steps
1. [Option A - describe and estimate effort]
2. [Option B - describe and estimate effort]
### Questions for You
- [Specific questions that would help narrow investigation]
Wait for human feedback before:
- Implementing new tests
- Pursuing a new hypothesis track
- Making any code changes
- Marking the investigation complete
Iteration Patterns
If tests reveal the cause:
- Document the root cause clearly
- Propose fix with test that verifies it
- Suggest additional regression tests
If tests are inconclusive:
- Identify what information is still missing
- Propose more targeted tests
- Consider expanding investigation scope
If human redirects investigation:
- Document why previous path was deprioritized
- Update hypotheses with new information
- Continue with new direction
Quick Reference Commands
# View current debug session
cat .debug-sessions/debug_*.md | tail -100
# Search for patterns in codebase
grep -rn "pattern" --include="*.cpp" --include="*.h" --include="*.hpp" .
# Find recent changes to suspect files
git log --oneline -20 -- path/to/file.cpp
# Show class/function usages
grep -rn "ClassName\|function_name" --include="*.cpp" --include="*.h" .
# Find all includes of a header
grep -rn '#include.*"suspect.h"' --include="*.cpp" .
# Build with debug symbols
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Run with Valgrind (memory errors)
valgrind --leak-check=full --track-origins=yes ./executable
# Run with AddressSanitizer output
ASAN_OPTIONS=detect_leaks=1:print_stats=1 ./executable
# Generate coverage report
lcov --capture --directory . --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
# Check for undefined behavior
./executable # if built with -fsanitize=undefined
# Examine core dump
gdb ./executable core
C++ Debugging Checklist
Common C++ issues to check:
Integration with User Workflow
- Always explain reasoning before taking actions
- Prefer asking clarifying questions over making assumptions
- Keep debug log updated as single source of truth
- Respect user's time - batch questions when possible
- Celebrate progress and acknowledge user insights