DEV Community

yureki_lab
yureki_lab

Posted on

How I Got My AI Coding Agent to Read Git Blame Before It Refactors Anything

TL;DR

I gave my autonomous coding agent a rule: before touching any function you didn't write, check git blame and read the commit message that introduced it. It sounds trivial, but it cut "confident but wrong" refactors dramatically — and it also taught me a few things about why AI agents make that mistake in the first place. Here's the workflow, the failure modes it doesn't fix, and what I'd change next.

The Problem

A few months into running an autonomous coding agent (built on Claude Code) across a handful of real projects, I noticed a pattern in its failures. It wasn't crashing. It wasn't writing broken code. It was doing something worse: writing code that looked correct and was subtly wrong, because it didn't understand why the existing code was weird.

Some concrete examples from my logs:

  • A setTimeout(fn, 0) that looked like a bug. It wasn't — it was a deliberate workaround for a browser paint-order issue from two years earlier. The agent "cleaned it up" and removed it. The bug came back three days later.
  • A retry loop with a suspiciously specific max_retries = 7. Someone had tuned that number against a flaky third-party API. The agent rounded it to 3 because "that's more standard." Timeouts spiked.
  • A duplicated validation check that looked like copy-paste laziness. It was actually defense-in-depth against a race condition between two services. The agent deduplicated it. The race condition came back.

None of these were reasoning failures in the traditional sense. The agent's logic, given what it could see, was completely sound. The problem was that it could only see the current state of the code — not the history that explained why the current state existed. It was optimizing for local readability without knowing it was destroying institutional memory that had never been written down anywhere except a commit message from 18 months ago.

That's a really common trap for both humans and agents: code that looks arbitrary is often not arbitrary, and the only evidence is buried in history you didn't think to check.

How I Solved It

The fix ended up being simpler than I expected: force a "why does this exist" step before any edit to code the agent didn't just write itself in the current session.

The workflow

flowchart TD
    A[Agent wants to edit function X] --> B{Did I write X\nthis session?}
    B -- yes --> E[Edit directly]
    B -- no --> C[git log -L for X's line range]
    C --> D[git blame + read commit message\nand linked PR/issue if any]
    D --> F{Commit message explains\nan intentional constraint?}
    F -- yes --> G[Preserve constraint,\nnote it in the edit rationale]
    F -- no / just a feature add --> E
Enter fullscreen mode Exit fullscreen mode

In practice, this is a short instruction block in the agent's operating rules, roughly:

Before modifying any function or block you did not author in this
session, run:

  git log -L <start_line>,<end_line>:<file> --follow -3

Read the commit messages. If a commit message references a bug fix,
workaround, race condition, or specific numeric tuning, treat that
code as load-bearing until proven otherwise. State explicitly in your
plan why it's safe to change, or leave it alone.
Enter fullscreen mode Exit fullscreen mode

The key design choice: this isn't "always read full git history for every file" (way too slow, way too much context burned). It's scoped to the exact line range being touched, using git log -L, which git supports natively for tracking a function or line range across its whole history — including through renames with --follow.

Here's a real trimmed-down transcript from a session where this caught something:

$ git log -L 42,45:src/sync/retry.py --follow -3

commit 8f3a1c2
Date:   Tue Feb 3 2026
    Bump retry ceiling to 7 — vendor API rate-limits burst
    to 5 req/s with a ~6s cooldown, saw premature failures
    at max_retries=3 in prod (incident #482).

@@ -39,7 +39,7 @@ def sync_with_retry(payload):
     backoff = min(2 ** attempt, 30)
-    max_retries = 3
+    max_retries = 7
Enter fullscreen mode Exit fullscreen mode

Agent's plan output after reading this:

max_retries=7 is tuned against a specific vendor rate limit (incident #482), not a magic number. Leaving it as-is. Will add a comment linking the commit so this doesn't get "cleaned up" again.

That last line — adding a comment that explains the why, not just the what — turned out to be one of the highest-leverage side effects. The agent started closing the same information gap for the next agent (or human) that touches the code, instead of just avoiding the mistake itself once.

Where I plugged this in

I didn't want this running on every single edit — that's wasted tokens and wasted time on greenfield code with no history worth reading. I gated it behind a simple heuristic: trigger the git-blame check only when the agent is about to modify (not create) a block of ≥3 lines it didn't write in the current session, and skip it entirely for pure additions, new files, and formatting-only changes.

The false positive that almost made me rip it out

Early on, this rule was too aggressive and nearly backfired. I initially had the agent treat any commit message longer than one line as a sign of "intentional, load-bearing code" — which meant it started refusing to touch perfectly ordinary code that just happened to have a verbose commit message ("Refactored the query builder to use the new ORM syntax, also fixed a typo in the docstring, see ticket #204 for context"). That's not a constraint, that's just a chatty commit. The agent got overly cautious and started asking for confirmation on things that were completely safe to change.

The fix was narrowing the trigger condition to specific keywords and patterns in the commit message — "fix", "workaround", "race", "regression", "incident", specific numeric tuning changes, or a linked ticket/incident number — rather than "message exists and is long." That cut the false-positive rate way down without losing the cases that actually mattered. It's a good reminder that giving an agent more context isn't automatically better; the context has to be filtered for signal, or you just trade one failure mode (ignorant edits) for another (paralyzed edits).

What it costs

Worth being honest about the overhead: each git log -L check adds maybe 1-3 seconds and a few hundred tokens of context per triggered edit. On a session with heavy refactoring — say 40-50 edits to existing code — that's a real but modest tax, not a rounding error. I measured it once on a mid-sized refactor session: total wall-clock time went up by about 6%, token usage by about 4%. In exchange, I stopped seeing the "silently reintroduced bug" pattern that used to show up roughly once a week across my projects. That trade was an easy yes for me, but it's worth measuring on your own workload rather than assuming it's free.

Lessons Learned

  1. Code history is a form of context the model can't infer — you have to hand it over. An agent reasoning purely from the current file state will always favor "clean" over "correct" when the two silently diverge. It has no way to know it's making that tradeoff unless you give it the tool to check.

  2. git log -L is underrated for this. Most people (and most agents, left to their own devices) reach for git blame on the whole file, which is noisy. Scoping to the exact line range with -L and following renames gives a much cleaner signal with far less token spend.

  3. The fix compounds if the agent writes the "why" back into the code. The real win wasn't the agent avoiding a bad edit once — it was the agent leaving a trail (a comment, a commit message reference) so the next pass, human or AI, doesn't have to rediscover the same history from scratch.

  4. This doesn't catch everything, and I stopped pretending it would. If the original commit message is bad ("fix bug", "update logic"), this workflow finds nothing useful. Garbage history in, garbage signal out. It also doesn't help with tribal knowledge that was never committed anywhere — a Slack thread, a verbal decision, a ticket in a system the agent can't reach.

  5. Gate it, don't blanket it. Running a history check before every single edit sounds safer but isn't — it burns context on code that has no relevant history (new files, first-pass scaffolding) and trains you to ignore the output because it's mostly noise. Scoping it to "editing code you didn't just write" made the signal worth reading every time it fired.

What's Next

I'm looking at extending this same instinct to linked issues and PR discussions, not just commit messages — a lot of the "why" for gnarlier decisions lives in a review comment thread, not the commit itself. The harder problem is doing that without turning every edit into a slow round-trip through an issue tracker API. Scoping will matter even more there.

Wrap-up

If you're running an autonomous or semi-autonomous coding agent and it keeps "fixing" things that come back to bite you, check whether it's reading history at all before it edits. It's a small addition with an outsized effect on trust.

If this was useful, I write about running AI coding agents on real projects pretty regularly — follow me here on Dev.to for more of these, and let me know in the comments if you've solved the "tribal knowledge" gap differently.

Top comments (1)

Collapse
 
daymondhyper profile image
DaymondHyper

Bookmarked. The part about evaluation being mandatory is exactly what I keep missing in my own experiments. How do you measure regressions, a separate test set?