Agentic Coding

Posts on AI-assisted and agent-based development.

Better AI Bug Fixes: One Rule and One Skill

While letting guests create live challenges in Draft Chess, I hit a bug: a guest host who opened their room’s settings and clicked Back to Lobby found the challenge gone, and could not create another (“You are already in a live game”). The agent found the rooms sitting in setup, a status meant for rooms with two players, and wrote restoreSoloLiveSetupRooms: a helper that walked every room on every lobby broadcast and flipped solo setup rooms back to waiting. Tests passed, including a new one for the helper. The test was correct. It tested the helper.

In review I asked why the status should change at all when the host walks between the room and the lobby. Was it to cover the host clicking Ready? The agent checked, agreed at once that Ready never touches room status and that no status change was needed, and removed the helper. The real cause was that a host re-entering their own room was being seated as a second player; fix the join flow and the helper has nothing to do. The agent found all of this in minutes once asked. The problem is that it needed to be asked. Nothing in its own process had required it to explain why a one-person room was in setup before writing code to undo it.

Call this a symptom patch: code that corrects a bad result where you noticed it, without touching the code that produced it. It teaches the system to tolerate a bug instead of removing it. This was not the first time an agent handed me code that did not need to exist, and a stronger model does not close the gap: it can still write a more elegant symptom patch with a better name, which is worse, because it is harder to recognise. The name is usually the tell. restoreX and ensureValidY imply X is supposed to drift and needs rescue. Treat restore, normalize, heal, reconcile, fix-up as clues; each is a question wearing a verb. Who produced this, and is correcting it here the right response?

Two pieces of configuration address it: a short always-on rule and and the debugging skill below.

The rule

My repo already had a prefer-smallest-fix rule. The agent followed it. The problem is that “smallest” was read as “closest to the symptom”, and a five-line helper at the symptom site is small. The rule now opens with a check that fires on the shape of the code rather than on the task label, since in my case nobody had called it a bug fix:

## Before adding corrective code

When a change adds an override, filter, special case, retry, fallback, or
state-repair helper to correct a result that is already wrong:

- Identify the code path that produced the result and verify the
  explanation against that code. A plausible story is not a cause.
- Fix the faulty producer or the mistaken consumer assumption where it occurs.
- Keep a correction only for existing data, supported external input, or
  documented concurrency, and say why preventing the bad state is not enough.
- In the change summary, state the verified cause and justify any retained
  correction. If the cause is unknown, say so and stop for review before
  adding the corrector.

Repair-verb names (`restore*`, `normalize*`, `heal*`, `reconcile*`,
`ensure*`) on code that mutates domain state are a cue to run this check.

The last bullet is the one that changes behaviour. An unknown cause no longer produces a quiet helper; it produces a sentence in the summary and a pause. The rest of the file (keep the change small, keep verification proportionate) is in the Gist.

The rule file has alwaysApply: true in its frontmatter, so it is in context on every turn. That matters here. The agent that writes a symptom patch believes it is writing the real fix, so it will not go looking for guidance; the check has to already be present. Rules scoped by file glob or chosen by description load only sometimes, which defeats the purpose. If you already have a smallest-fix or minimal-diff rule, merge this section into it rather than adding a second file; otherwise take the whole file from the Gist.

The skill

The rule interrupts the agent at the moment it reaches for a corrector. What to do instead is a procedure, needed only while fixing a bug, so it goes in a skill rather than in always-on context. .cursor/skills/debug-at-source/SKILL.md:

---
name: debug-at-source
description: Investigate and fix a reproducible bug by tracing the code path
  that produced the bad result. Use when fixing bugs, wrong values, or
  unexpected state.
---

# Debug at the source

1. Reproduce the failure. Write down the exact wrong value or state.
2. Trace back from where it is observed to the code that wrote it. Start with
   the most likely handler and follow its calls until the explanation is
   supported or disproved by code you have read.
3. Keep two lists: verified (you read the code) and hypothesis (you have not).
   Do not design a fix on a hypothesis.
4. Decide which applies:
   a. The producer is wrong: fix it.
   b. A consumer assumed something false: fix the assumption.
   c. The input is a documented condition you must handle (external data,
      rows already written, a race you cannot close): handle it, and name
      the condition in the code.
5. Test the original failing path and assert the intended behaviour at the
   end of it, not that a corrector ran.
6. If repair code exists for this bug, remove it unless it still has
   legitimate work under 4c.

Steps 2 and 3 exist because the agent never had a verified explanation for the bad status. It had an observation (solo rooms in setup) and went straight to correcting it; the one question a reviewer asked was enough to collapse the fix, which means the agent could have asked it itself. Step 5 exists because of the helper test. It verified the proposed implementation and could not challenge the diagnosis. The test that would have caught the bug walks the failing path and states what must hold at the end: a host reopening their own room still has one seat, the room is still waiting, and someone else can still join. The helper could not have satisfied that, because the helper fixes status after the fact and the test asks whether the host was seated as the host.

Cursor selects skills by matching the task against the description, so the description is part of the configuration: it names the activity (fixing a reproducible bug), not the mistake the agent would have to recognise in itself. For predictable activation, invoke it explicitly when starting bug work, with /debug-at-source or “use the debug-at-source skill”.

Cost

Always-applied text costs context on every turn whether or not its condition is met; an instruction that demands investigation costs work on every edit where the investigation adds nothing. That split is why the short check lives in the rule and the procedure lives in the skill. I now ask the agent for an estimated token cost whenever it proposes a rule or skill, how often it will load and what extra reading it asks for. The estimate is a forecast; the check is observed usage and review effort afterwards.

Closing

An agent required to identify the producing path will either find the bug or say “I don’t know what sets this,” which a reviewer can act on. Symptom patches are dangerous because they are silent. A check that forces the author to say I am correcting a value I don’t understand turns an invisible shortcut into a visible decision, and visible decisions get reviewed.

Contact

Let’s talk about what you need. I offer flexible pricing for early-stage startups and longer-term projects.

Form validation failed.