Debugging Strategies
Description
Systematic, hypothesis-driven debugging methods to locate root causes and verify fixes when code misbehaves or when facing regressions.
When to Use
Help me debug this code using hypothesis-driven techniques. | Show me how to perform a stack-trace analysis. | I think this is a regression; help me bisect it. | How can I reproduce a bug and verify fixes? | Guide me through a systematic debugging approach.
Use Cases
Diagnose why a function returns incorrect results. | Trace a failing async chain to the root cause. | Identify regression after a code change. | Isolate bugs using targeted logging and tests. | Verify fixes with controlled reproductions and checks.
SKILL.md Content
---
name: debugging-strategies
description: "Systematic, hypothesis-driven debugging methods to locate root causes and verify fixes when code misbehaves or when facing regressions."
metadata:
tags: "debugging, hypothesis-driven-debugging, stack-trace-reading, bisection, git-bisect, log-analysis, root-cause-analysis"
source: "https://skilldb.dev/skills/autonomous-agent-skills/debugging-strategies"
pack: "autonomous-agent-skills"
category: "Autonomous Agents"
---
# Debugging Strategies
## When to use this skill
Use when the user says things like:
- "Help me debug this code using hypothesis-driven techniques."
- "Show me how to perform a stack-trace analysis."
- "I think this is a regression; help me bisect it."
- "How can I reproduce a bug and verify fixes?"
- "Guide me through a systematic debugging approach."
You are an autonomous agent that debugs methodically. You never guess randomly or apply changes hoping something sticks. Every debugging action you take is driven by a hypothesis, and every fix you apply is verified.
## Philosophy
Debugging is an exercise in scientific reasoning. You observe a symptom, form a hypothesis about its cause, design an experiment to test that hypothesis, and interpret the result. This cycle repeats until you reach the root cause — not just a symptom. Resist the urge to change code before you understand what is actually wrong.
## Core Techniques
### Hypothesis-Driven Debugging
Before touching any code, articulate a theory about what is going wrong:
1. **State the symptom precisely.** "The function returns null" is better than "it doesn't work." Include inputs, expected output, and actual output.
2. **Form a hypothesis.** "The null return happens because the cache lookup on line 42 misses when the key contains Unicode characters."
3. **Design a test for the hypothesis.** Read the relevant code path, add a targeted log, or write a minimal reproduction.
4. **Interpret the result.** If the hypothesis is wrong, that is still progress — you have eliminated a possibility. Refine and repeat.
### Reading Stack Traces
- Read from the bottom up. The root cause is usually the deepest frame that belongs to the project's own code, not library internals.
- Identify the exact line number and the values of relevant variables at that point.
- Distinguish between the throw site (where the error originates) and the call site (where it propagates). Fix at the throw site when possible.
- In async or callback-heavy code, look for "caused by" chains or separate async stack segments.
### Bisection Strategies
When you cannot pinpoint the cause by reading code:
- **Git bisect.** If the bug is a regression, use `git log` to identify candidate commits, then narrow down with bisection. This is the fastest way to find regressions in large histories.
- **Code bisect.** Comment out or bypass half the suspect logic, check if the bug persists, and narrow the search space by half each iteration.
- **Input bisect.** If a large input triggers the bug, reduce it to a minimal failing case by removing half the input at a time.
### Reproducing Issues
A bug you cannot reproduce is a bug you cannot confidently fix.
- Create the simplest possible reproduction: a standalone test, a script, or a minimal set of steps.
- Control for environment differences: OS, runtime version, configuration, database state.
- If the bug is intermittent, look for race conditions, timing dependencies, uninitialized state, or external service flakiness.
### Isolating Variables
Change one thing at a time. If you change two things and the bug disappears, you do not know which change fixed it — or whether the combination masked a deeper issue.
- Revert to the broken state, apply only one candidate fix, and verify.
- Use feature flags, environment variables, or conditional logic to toggle suspect code paths in isolation.
### Log Analysis
- Search logs for the first occurrence of the error, not the last. The root cause appears first; subsequent errors are often cascading failures.
- Correlate timestamps. If you see an error at 14:02:03, look for unusual events in the seconds before it.
- Add structured, temporary logging at strategic points (function entry/exit, branch decisions, variable values) rather than scattering print statements everywhere.
- Always remove temporary debug logging before finalizing a fix.
### Understanding Error Messages in Context
- Read the entire error message, including parts after the first line. Many developers stop at "NullPointerException" and miss the message text that says exactly which reference was null.
- Search the project codebase for the error message string to find where it is thrown. This reveals the condition that triggered it.
- For third-party errors, check the library's source code or documentation before searching the web.
## Best Practices
- **Verify the fix, not just the absence of the symptom.** Write a test that fails before the fix and passes after. Confirm the test targets the root cause.
- **Check for related bugs.** If a boundary check was missing in one place, the same pattern may be wrong elsewhere in the codebase.
- **Explain the bug before writing the fix.** If you cannot explain why the bug happens in one sentence, you may not have found the root cause.
- **Preserve the reproduction.** Turn your minimal reproduction into a regression test so the bug cannot silently return.
- **Timebox exploration.** If you have spent significant effort on one hypothesis without progress, step back and reconsider your assumptions. Re-read the original error report.
- **Read before writing.** Spend more time reading the surrounding code than writing changes. Understanding the system's intended behavior prevents introducing new bugs.
## Anti-Patterns
- **Shotgun debugging.** Making multiple unrelated changes hoping one of them fixes the problem. This wastes time, introduces new bugs, and teaches you nothing.
- **Fixing the symptom instead of the cause.** Adding a null check around a crash site without understanding why the value is null. The null is a symptom; the missing initialization or broken data flow is the cause.
- **Debugging by rewriting.** Rewriting a function from scratch because it has a bug, rather than understanding the specific flaw. The rewrite often introduces different bugs.
- **Ignoring test failures elsewhere.** If your fix causes other tests to fail, do not skip or disable those tests. They are telling you something about your fix.
- **Assuming the bug is in the framework or library.** It almost never is. Check your own code first — your assumptions, your inputs, your configuration.
- **Not reading the error message.** Many bugs are solved by carefully reading what the error actually says rather than what you assume it says.
- **Leaving debug code in place.** Temporary print statements, commented-out blocks, and hardcoded test values must be removed before the fix is finalized.