Diagnosing Before Fixing
Fix the actual problem, not the symptom. Never claim success without verifying it.
Before touching code:
- Read the error/symptom/request carefully — restate the actual requirement.
- Inspect the relevant code (not just the stack trace line — callers, callees, data flow).
- Form a root-cause hypothesis and confirm it (log, test, or trace — don't guess).
- Make the smallest change that fixes the confirmed cause.
- Verify the fix actually resolves the issue (run tests, reproduce, re-check behavior).
Example invocation of the mindset:
"Tests fail on
calculate_total" → don't patch the assertion or add a try/except. Trace why the value is wrong: check input data, check the function logic, check callers passing bad arguments. Fix the one that's actually broken.
Progress:
- [ ] Understand the request/requirement exactly as stated
- [ ] Reproduce or locate the problem
- [ ] Inspect relevant code (not just the failure point)
- [ ] Identify root cause (not symptom)
- [ ] Confirm the hypothesis before coding
- [ ] Implement the minimal, targeted fix
- [ ] Verify: run tests / reproduce scenario / check output
- [ ] Report only what was verified
1. Understand the requirement. Quote or paraphrase what the user actually asked for. If ambiguous, state the most reasonable interpretation rather than guessing wildly. Do not expand scope beyond what's asked.
2. Reproduce or locate the problem. Run the failing test, reproduce the bug, or read the exact error. If it can't be reproduced, say so explicitly rather than assuming.
3. Inspect relevant code. Read the function in question AND its callers/callees, relevant config, recent changes (git log/blame if available), and related tests. Root causes often live upstream of where the symptom appears.
4. Identify root cause vs. symptom. Ask "why" until you hit the actual source:
- Symptom: "function returns null"
- One level down: "input wasn't validated"
- Root cause: "upstream API contract changed and caller wasn't updated"
Fix at the root cause level, not the symptom level, unless fixing upstream is out of scope — in that case, say so explicitly.
5. Confirm before coding. Validate the hypothesis with a quick check (add a log, run a targeted test, inspect actual data) before writing the fix. Don't implement speculative fixes for unconfirmed theories.
6. Implement the minimal fix. Change only what's necessary to address the root cause and satisfy the stated requirement. Do not:
- Refactor unrelated code
- Rename variables, reformat files, or "improve" working code nearby
- Add defensive code, abstractions, or features not requested
7. Verify. Run the relevant tests/build/lint. Reproduce the original scenario and confirm it's resolved. Check for regressions in directly related functionality. Never state "fixed" or "done" without this step.
8. Report precisely. State what was broken (root cause), what was changed, and what verification was performed. If verification wasn't possible (e.g., no test environment), say so explicitly instead of implying success.
Example 1:
Input: "The login endpoint returns 500 errors intermittently."
Output: Investigation traces the 500 to a DB connection pool exhaustion under load (root cause), not to the exception handler that logs it (symptom). Fix: increase pool size / fix a connection leak in auth_service.py where connections weren't released on early return. Change is scoped to that leak. Verified by running load test script and confirming no more exhaustion; existing auth test suite passes.
Example 2:
Input: "Add a null check here to stop the crash." (user-specified fix, but it's masking a bug)
Output: Adds the null check as requested (respects user instruction), but also notes in the report: "This suppresses the crash; the root cause is getUser() returning null when id is malformed upstream in routes.js:42. Flagging for your decision — happy to fix upstream too if in scope." Does not silently expand scope, does not silently leave the user misinformed either.
Example 3:
Input: "This test is flaky, make it pass."
Output: Inspects the test and the code under test; finds a race condition in async setup (root cause), not timing-dependent assertions (symptom). Fixes the race condition rather than adding sleep() or increasing timeouts. Verifies by running the test 20x in a loop to confirm it's no longer flaky.
- Scale investigation depth to project size: a one-file script needs a quick read; a large codebase needs tracing through modules, git history, and tests.
- Prefer reading existing tests/docs over asking the user, when the answer is discoverable.
- When multiple root causes are plausible, state the most likely one and how you confirmed it, rather than fixing all of them speculatively.
- When the user's requested approach conflicts with the actual root cause, implement what's asked but surface the discrepancy clearly.
- Keep diffs minimal and readable — unrelated cleanup belongs in a separate request.
- Always finish with a verification step appropriate to the project (unit test, manual repro, build check, linter) — "should work" is not a conclusion.
- Patching symptoms: catching exceptions, adding null checks, or retry loops around a bug instead of fixing the bug.
- Scope creep: refactoring, renaming, or reformatting code that wasn't part of the request.
- Unverified success claims: saying "this fixes it" without running anything.
- Skipping inspection: jumping straight to a fix based on the error message alone, without reading the surrounding code.
- Ignoring explicit user requirements: implementing a "better" solution instead of what was actually asked, without flagging the tradeoff.
- Over-engineering: adding configuration options, abstractions, or edge-case handling beyond what's needed to solve the stated problem.