Using LLMs to Fix Bugs Faster: How We Located a Long-Standing Defect in Nine Minutes

    This article presents a practical five-step workflow for using large language models as debugging accelerators. It covers how to structure bug reports, build a minimal reproduction, prepare context by call-chain slicing, run hypothesis–verification loops, and ship minimal patches with regression tests — turning hard-to-fix legacy defects into repeatable engineering work.

    Using LLMs to Fix Bugs Faster: How We Located a Long-Standing Defect in Nine Minutes

    1. The Core Problem: Why Long-Standing Bugs Stay Unfixed

    Long-lived defects are rarely difficult because of the code itself. More often, they are difficult because the available information is incomplete. A bug that has survived for months or even years usually has several common characteristics:

    • Hidden trigger conditions: It only appears under specific data distributions, timing sequences, or concurrency windows that normal testing rarely covers.
    • Missing evidence: Logs are truncated, stack traces are swallowed, and reproduction steps are scattered across different people’s memories.
    • Long attribution chains: The visible failure and the real root cause may be separated by multiple modules, making the code-reading cost extremely high.
    • High change risk: Legacy code often lacks test protection, so changing one line can affect a much larger area.

    The bottleneck in fixing these defects is essentially the cost of building large-scale contextual understanding within a limited time. Large language models are particularly good at rapidly generating hypotheses and reasoning through code paths within a given context. That is exactly what makes them valuable in a debugging workflow.

    It is important to be clear about one thing: a model is not an automatic bug-fixing machine. It is a high-quality hypothesis generator and code-reading accelerator. Whether you can close the loop in minutes depends on the quality of the context you provide and the rigor of your verification.

    2. Breaking Down the Debugging Task into Five Deliverable Steps

    To make a model genuinely useful in fixing a bug, rather than producing plausible but vague suggestions, it helps to structure the process into five stages:

    1. Structure the symptom: Translate an intermittent error into a clearly defined input, environment, expected output, and actual output.
    2. Build a minimal reproduction: Create the smallest possible test case that reliably reproduces the issue locally. This becomes the baseline for all later verification.
    3. Trim the context: Provide only the files, functions, and data samples related to the call chain. Avoid noise that dilutes attention.
    4. Run a hypothesis–verification loop: Ask the model for multiple candidate root causes and a verification method for each. Disprove or confirm them one by one using the minimal reproduction.
    5. Patch and prevent regression: Produce a minimal patch and add an automated test that covers the defect.

    The key point of this workflow is that every step produces something that can be evaluated. Without a minimal reproduction, none of the model’s conclusions can be verified. Without a regression test, a fix may simply plant a new hidden problem.

    3. Context Organization Determines Output Quality

    Many failed attempts at model-assisted debugging are caused by poor context preparation rather than weak model capability. The following principles help.

    3.1 Provide a Call-Chain Slice, Not the Whole Repository

    A model’s effective attention is limited. Dumping an entire project into the prompt only introduces noise. The right approach is to follow the exception stack and cut out a call chain from the entry point to the failure point. Include only:

    • The entry function and trigger condition
    • Parameter transformation logic in intermediate layers
    • The complete function body at the failure point
    • Relevant data structure definitions
    1. [Entry] handle_request(data)
    2. → [Transform] normalize(data) # Parameter normalization
    3. → [Branch] dispatch(payload) # Routing and state decision
    4. → [Failure] build_output(result) # Exception thrown here

    By providing code along this chain, you keep the model focused on where and how the data changes, instead of letting it wander through unrelated modules.

    3.2 Replace Text Descriptions with Data Samples

    Saying “some inputs fail” is far less effective than providing three concrete examples:

    1. Input A (normal): { "type": "batch", "items": 12, "ts": 1698000000 }
    2. Input B (failed): { "type": "batch", "items": 0, "ts": 1698000000 }
    3. Input C (failed): { "type": "single", "items": 1, "ts": 0 }

    With contrasting samples, the model is much more likely to identify hypotheses related to boundary values, empty collections, and missing timestamps.

    3.3 State Constraints Explicitly to Avoid Over-Engineering

    Clearly stating engineering constraints in the prompt can significantly reduce unrealistic large-scale rewrite proposals:

    • Only modify the failing function and its direct callers.
    • Do not change external interface signatures.
    • Do not introduce new external dependencies.
    • The patch must remain backward compatible.

    Constraints like these pull the model away from rewriting an entire module and toward a minimal viable fix, which directly affects whether the patch can survive code review.

    4. The Hypothesis–Verification Loop: Turning Model Output into Falsifiable Conclusions

    A model often proposes more than one root cause. The efficient approach is not to pick one and trust it blindly, but to require a verification action for every hypothesis:

    Hypothesis Verification Method Acceptance Criteria
    Empty collection is not short-circuited Step through with Input B Whether an exception is thrown during aggregation
    Missing timestamp is treated as valid Trace branch behavior with Input C Whether the wrong branch is taken
    Type-checking order causes misrouting Print dispatch input Whether the type matches the selected branch

    Each verification is based on the minimal reproduction, so the cost is very low and convergence is fast. In practice, the real root cause is usually identified within two or three rounds. The remaining time is spent writing and validating the patch.

    It is worth noting that the verification methods proposed by the model are themselves useful output. Even if a hypothesis is disproven, the observation points it leaves behind — log locations, breakpoints, or assertions — can still be reused later.

    5. Writing the Patch and Preventing Regression

    Once the root cause is confirmed, the patch should meet three conditions:

    1. Minimal change: Fix only the root cause. Do not mix in style cleanup or refactoring. This reduces review and rollback cost.
    2. Explicit defense: Add clear guard conditions for edge cases such as empty collections, default values, and invalid types, rather than relying on callers to behave correctly.
    3. Observability: Add structured logging in key branches so that similar issues can be located quickly in the future.
    1. function build_output(result):
    2. if result is empty: # Explicit short-circuit to avoid downstream aggregation failure
    3. log("empty result", trace_id)
    4. return empty_output()
    5. if not valid_ts(result.ts): # Missing timestamp falls back to default branch
    6. result.ts = default_ts()
    7. return aggregate(result)

    Before merging the patch, add a regression test that covers the failing input from the minimal reproduction. The value of this test is not only preventing regression. It also turns a past mistake into shared team knowledge.

    6. Cost Control: Running the Full Loop on a Small Budget

    When using models for debugging on a limited budget, the key is to spend calls on high-value steps rather than trying to resolve everything in a single prompt. Consider the following strategies:

    • Layered calls: Use short context for hypothesis generation first. Once a direction is identified, use the full call chain for patch writing. Avoid sending large code blocks every time.
    • Reuse cached context: A call-chain slice for one module can be reused for multiple defects, reducing repeated input.
    • Batch verification: Combine multiple candidate hypotheses into one prompt and ask the model to provide a separate verification step for each, reducing back-and-forth.
    • Human fallback: Model output must always be verified against the minimal reproduction. Never merge an unverified patch directly into the main branch.

    One reminder: the benefit of model-assisted debugging depends heavily on how testable the project is. If a minimal reproduction is hard to build, logs are missing, and modules are tightly coupled, even a strong model cannot compensate for gaps in engineering foundations. While standardizing the debugging process, teams should also invest in testability and observability.

    7. A Reusable Methodology

    Abstracting the practices above yields a tool-agnostic process:

    1. Structure the symptom: Include all four elements — input, environment, expected output, and actual output.
    2. Prioritize minimal reproduction: Do not enter the root-cause phase without a reproduction.
    3. Trim context by call chain: Provide code slices, data samples, and engineering constraints.
    4. Make every hypothesis falsifiable: Each root-cause hypothesis should come with a verification action.
    5. Keep patches minimal and lock them with regression tests: Fix one spot, protect one spot.

    The value of this workflow is that it turns debugging from an activity dependent on personal experience into engineering work that is repeatable, reviewable, and measurable. The model plays the role of an accelerator rather than a decision maker. It reduces the time spent reading code and generating hypotheses, while verification and final decisions remain with the engineer.

    When teams adopt these five steps as daily practice, the average time to fix long-standing defects drops noticeably, and fix quality becomes more stable. What truly determines efficiency is never one magical prompt, but whether the process itself is rigorous enough.