DEV Community

Kunal
Kunal

Posted on Originally published at kunalganglani.com

How to Prevent AI Coding Assistant Repeating Mistakes [2026]

Originally published at kunalganglani.com — read it there for inline code, hero image, and live links.

Your AI coding assistant can fix a bug at 10:07am and reintroduce the same bug at 10:31am.

If you want to prevent AI coding assistant repeating mistakes, you need one prerequisite most teams skip: the fix has to become an artifact in your repo (rules + tests + a “mistake memory” entry). If it only lives in chat, it dies in the next context reset.

This post is a tool-agnostic playbook I wish more teams used. It works whether you’re in Cursor, Copilot, ChatGPT, or Claude Code. The point is simple: stop treating “don’t do that again” as a vibe. Treat it as engineering.

Checklist: prevent AI coding assistant repeating mistakes

Use this as your standard operating procedure. It’s intentionally boring.

  1. Write the mistake down in-repo (a single structured entry). Not in tool settings.
  2. Pin the banned pattern (what to stop doing) and the preferred pattern (what to do instead).
  3. Attach a regression test that fails if the mistake comes back.
  4. Store the patch: link the commit or save the diff that fixed it.
  5. Add a short guardrails prompt the assistant must follow before proposing changes.
  6. Make instructions scoped: repo-wide for global rules, path-specific for local rules.
  7. Budget your rules: top 10 rules win. Everything else gets pruned or moved to docs.
  8. Automate retrieval: when files/tests/errors match, inject the relevant mistake entry + patch.
  9. Gate on CI: the same test that caught the bug blocks PRs.
  10. Assign ownership: someone curates mistake memory monthly. If nobody owns it, it turns to trash.

The rest of this post shows exactly what to put in the repo, plus working code to wire it into pre-commit and CI.

What is “mistake memory” for AI coding assistants?

Mistake memory is a versioned, repo-stored log of recurring failures (bugs, style violations, unsafe patterns) that includes the symptom, root cause, banned pattern, preferred pattern, and the regression test/patch that proves the fix.

This is not the same as generic “coding standards.” It’s specifically for the painful stuff your assistant keeps relapsing on.

Why repo-stored? Because most assistants start fresh more often than you think. Anthropic is pretty explicit about this: “Each Claude Code session begins with a fresh context window” and persistence comes from instruction files plus optional auto memory that Claude writes from your corrections and preferences (Anthropic).

Even if your tool has some notion of memory, leaning on it is how teams lose hard-won lessons the moment they:

  • switch tools
  • change machines
  • hit compaction (/compact)
  • work in a new folder
  • onboard a new engineer

I’ve shipped enough systems to be allergic to “tribal knowledge.” It feels fast right up until you hit scale and everything starts slipping through the cracks. In my Walmart conversational commerce chatbot work, the biggest quality jumps came when we turned one-off fixes into repeatable guardrails. We saw a 400% product engagement lift with a retrieval-heavy system, and the uncomfortable lesson was that process beats heroics.

Why do AI coding assistants forget fixes and reintroduce bugs?

There are four usual culprits. If you recognize your setup in any of these, you’re not doing anything “wrong.” You’re just missing some infrastructure.

1) The fix never made it into durable context

If your only durable context is “whatever is in the chat transcript,” you’re building on sand.

Most AI coding tools have a context window. Even the big ones. Context gets truncated, summarized, or compacted. That compaction is lossy by design.

2) The constraints were underspecified

OpenAI’s prompt engineering guidance is blunt here: reliability improves when you make constraints explicit and structure the task as steps instead of vibes (OpenAI).

“Fix the bug” is not a constraint. “Fix the bug, don’t change public APIs, add a regression test, and run pnpm test” is.

3) The assistant optimizes for local completion, not global correctness

Coding assistants are implicitly rewarded for:

  • producing something that compiles
  • satisfying the immediate request
  • moving fast

They are not rewarded for preserving your repo’s invisible invariants unless you spell them out and enforce them.

4) Multi-step work amplifies inconsistency

The more steps you chain, the more chances to slip. In the MetaGPT paper, the authors call out “logic inconsistencies due to cascading hallucinations caused by naively chaining LLMs” and propose SOPs + verification to reduce errors (Sirui Hong).

That’s academic language for a practical reality. Agents relapse unless you build checks.

Repo instruction files that actually prevent relapse (repo-wide + path-specific)

If you do one thing after reading this post: put your constraints in the repository.

Different tools name this differently:

  • Claude Code: CLAUDE.md, AGENTS.md, and .claude/rules/ (Anthropic)
  • GitHub Copilot: copilot-instructions.md with repo-wide and path-specific instructions (GitHub)

The trick is not the file format. The trick is scope.

Repository custom instructions (repo-wide)

These are your global invariants. Keep them short. If you can’t fit them in ~30 lines, you’re writing a novel, not instructions.

Example copilot-instructions.md (works conceptually for any tool that reads repo instructions):

# Global guardrails

- Always add or update a regression test for any bug fix.
- Do not change public API signatures without explicit approval.
- Prefer small diffs. If change touches > 5 files, propose a plan first.
- After edits: run `pnpm test` and `pnpm lint`.
- Never disable existing tests to “make CI green”.
Enter fullscreen mode Exit fullscreen mode

That “never disable existing tests” line sounds obvious. It’s also the kind of “obvious” that prevents a 2am incident.

If you’re using Claude Code specifically, you’ll likely put the same guardrails into CLAUDE.md (or AGENTS.md if you want one file other tools can reuse).

If you’re trying to standardize team-wide, I’d rather see this in-repo than in personal tool settings. Same reason I baked compliance defaults into scaffolding when I built a SOC 2 scaffolding CLI at Rise People. “Compliance at PR time” is theatre. Compliance in the template ships.

Path-specific instructions (scoped rules)

Path-specific rules are how you avoid instruction fights.

You don’t want “all code is TypeScript” in the global file if you have a python/ folder. You want it scoped.

A simple pattern:

  • Root copilot-instructions.md for global rules
  • frontend/copilot-instructions.md for React conventions
  • api/copilot-instructions.md for backend conventions

GitHub documents both repo-wide and path-specific instruction support for Copilot (GitHub). Claude Code has similar scoping via .claude/rules/ and path-specific rules (Anthropic).

Minimal repo layout I recommend

Keep it boring and discoverable:

Artifact Purpose Typical owner
copilot-instructions.md or CLAUDE.md Global guardrails Tech lead
.claude/rules/ (optional) Scoped rules per path Domain owners
mistakes/ Mistake memory entries Whoever fixed the bug
mistakes/index.json Retrieval keys map Tooling/DevEx
tests/regression/ Regression tests Feature teams
.github/workflows/ci.yml CI gate runs regression suite Platform

If you’re building AI agents that work across repos, repo consistency matters more than tool choice.

The “Mistake Memory” template (with examples you can steal)

Here’s the spec most vendors won’t hand you because it makes their “memory” features look less magical. It’s designed to be:

  • human-reviewable
  • diff-friendly
  • easy to retrieve by strings (file path, test name, error text)

I use YAML because it’s readable, but JSON is fine.

Create: mistakes/M-0007-null-cache-key.yml

id: M-0007
title: "Never use user input as a cache key"
status: active
severity: high
introducedBy:
  tool: "ai-coding-assistant"
  date: "2026-09-19"

symptom:
  - "Cache hit rate drops to ~0%"
  - "Redis memory spikes"

rootCause:
  - "User-provided query string was used directly as cache key; highly variable inputs created unbounded key cardinality."

bannedPattern:
  - "cache.get(req.query.q)"

preferredPattern:
  - "cache.get(hash(normalizeQuery(req.query.q)))"
  - "Add TTL and max key size"

regressionTest:
  path: "tests/regression/cache_key_cardinality.test.ts"
  command: "pnpm test tests/regression/cache_key_cardinality.test.ts"

patch:
  commit: "a1b2c3d"
  files:
    - "api/search/cache.ts"

retrievalKeys:
  paths:
    - "api/search/cache.ts"
  tests:
    - "cache_key_cardinality"
  errorStrings:
    - "key cardinality"
    - "Redis OOM"

notes:
  - "If we move caching to CDN, revisit this rule."
Enter fullscreen mode Exit fullscreen mode

A few opinions I’ll stand behind:

  • bannedPattern must be concrete. Not “don’t do insecure stuff.” Put the exact anti-pattern.
  • preferredPattern must be executable. Give the assistant a shape it can paste and adapt.
  • regressionTest is not optional if you actually want to stop reintroductions.
  • notes is where you keep rules from going stale after the repo changes.

If you want to go deeper on the broader memory story for agents, I’ve written about agentic AI and agent orchestration patterns that make this kind of state manageable.

Patch-based retrieval: auto-surface the exact diff that fixed it

This is the highest-signal context you can hand a coding agent:

  • the failing test
  • the diff that made it pass

That’s “patch-based retrieval.” It’s the coding equivalent of RAG done right. Retrieve the one thing that worked before, not a pile of docs nobody reads.

I’m opinionated here because retrieval quality dominates outcomes. When we built the Walmart chatbot, the lesson was blunt: retrieval quality mattered more than model choice at scale, and it wasn’t close.

How to implement patch retrieval (simple version)

1) Keep your mistake entries in mistakes/*.yml.

2) Maintain a generated index file the assistant tooling can read quickly:

mistakes/index.json

{
  "M-0007": {
    "paths": ["api/search/cache.ts"],
    "tests": ["cache_key_cardinality"],
    "errorStrings": ["Redis OOM", "key cardinality"],
    "commit": "a1b2c3d"
  }
}
Enter fullscreen mode Exit fullscreen mode

3) Retrieval heuristic (good enough to start):

  • If the assistant is editing a file that matches any paths, inject that mistake entry.
  • If a test fails and its name matches tests, inject it.
  • If an error line contains any errorStrings, inject it.

A runnable retrieval script (Node)

This script reads:

  • git diff --name-only
  • last test output file (optional)

…and prints the relevant mistake entries + the patch commit.

// scripts/retrieve-mistakes.mjs
import fs from 'node:fs';
import { execSync } from 'node:child_process';
import path from 'node:path';

const indexPath = path.join(process.cwd(), 'mistakes', 'index.json');
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));

const changedFiles = execSync('git diff --name-only', { encoding: 'utf8' })
  .split('\n')
  .map(s => s.trim())
  .filter(Boolean);

const testLogPath = process.argv[2];
const testLog = testLogPath && fs.existsSync(testLogPath)
  ? fs.readFileSync(testLogPath, 'utf8')
  : '';

function matches(entry) {
  const paths = entry.paths || [];
  const tests = entry.tests || [];
  const errors = entry.errorStrings || [];

  if (changedFiles.some(f => paths.includes(f))) return true;
  if (tests.some(t => testLog.includes(t))) return true;
  if (errors.some(e => testLog.includes(e))) return true;
  return false;
}

const hits = Object.entries(index)
  .filter(([_, entry]) => matches(entry))
  .map(([id, entry]) => ({ id, ...entry }));

if (hits.length === 0) {
  console.log('No relevant mistake memory entries found.');
  process.exit(0);
}

console.log('Relevant mistake memory:');
for (const h of hits) {
  console.log(`- ${h.id} (commit ${h.commit})`);
}

console.log('\nTo view patches:');
for (const h of hits) {
  console.log(`git show ${h.commit} --stat`);
}
Enter fullscreen mode Exit fullscreen mode

That’s the retrieval engine. It’s dumb, fast, and already better than “remember what I said earlier.”

If you want to go full production, you can build a proper retrieval-augmented generation setup with embeddings. Start here anyway. This is one of those things where the boring answer is actually the right one.

Regression tests + CI gates: make relapse expensive

If a mistake can come back without failing something, it will come back.

Here’s the workflow I push on teams:

  1. Assistant proposes fix.
  2. Assistant adds regression test.
  3. CI runs the regression suite.
  4. PR cannot merge if it fails.

You don’t get reliability by “prompting harder.” You get it by making failures observable and blocking.

A runnable pre-commit hook (optional, but effective)

Use pre-commit to run just the regression tests that matter.

.pre-commit-config.yaml

repos:
  - repo: local
    hooks:
      - id: regression-tests
        name: run regression tests
        entry: bash -c 'pnpm test tests/regression'
        language: system
        pass_filenames: false
Enter fullscreen mode Exit fullscreen mode

This is intentionally blunt. If your regression folder gets too slow, shard it.

CI example (GitHub Actions)

.github/workflows/ci.yml

name: ci
on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install
      - run: pnpm test
      - run: pnpm test tests/regression
Enter fullscreen mode Exit fullscreen mode

Numbers matter here.

If your regression suite adds 2–5 minutes to CI, that’s annoying but survivable. If it adds 30 minutes, nobody will maintain it and you’ll quietly delete it “temporarily” one Friday.

If you care about measuring agent workflows and harness overhead (you should), read How to Measure AI Coding Agent Harness Overhead [2026]. I’m not guessing about this. I track it.

Tool-specific persistence: CLAUDE.md vs auto memory, and how to debug when instructions are ignored

You can do everything “right” and still watch the assistant ignore you. Usually because you don’t actually understand what the tool loads.

CLAUDE.md vs auto memory (persistence mechanisms)

In Claude Code, Anthropic describes two mechanisms:

  • CLAUDE.md / AGENTS.md: instructions you write
  • auto memory: notes Claude writes from your corrections/preferences

Claude also supports rules organization in .claude/rules/ and troubleshooting for when instructions seem lost after compaction (/compact) (Anthropic).

My stance: use auto memory as a convenience, not as governance. If it matters, commit it.

Auto memory (enable/disable, audit/edit)

If you’re going to rely on auto memory at all, you need two habits:

  • audit what got saved
  • delete stale or conflicting notes

Claude Code supports viewing/editing memory with /memory per their docs. The reason this matters is security and correctness. Auto memory can capture a preference that later becomes wrong.

If you’re working in a sensitive environment, treat memory as an AI security surface. Memory can be poisoned. It can also be exfiltrated.

Troubleshooting when the assistant isn’t following instructions

When someone tells me “it ignored my instructions,” most of the time it’s one of these:

  1. The file isn’t in the loaded scope. You put rules in the repo root, but the tool is operating in a subdirectory.
  2. Conflicting rules. Two instruction files say opposite things. The model flips a coin.
  3. Rules are too long. They get truncated or compacted.
  4. Your rule is non-actionable. “Write clean code” means nothing.
  5. The assistant never saw the failure. You didn’t paste the failing test output.

A practical debugging trick: make the assistant echo back its loaded guardrails.

Add this to your repo-wide instructions:

Before proposing changes, restate the relevant guardrails you are following (max 5 bullets).
Enter fullscreen mode Exit fullscreen mode

If it can’t restate them, it’s not following them.

If you want a deeper threat-model view of assistants ignoring constraints, you’ll like my write-ups on prompt injection and LLM security. Guardrails don’t exist in a vacuum.

Keeping mistake memory from turning into noise (pruning + ownership)

Mistake memory fails the same way wikis fail.

It grows. It gets stale. Nobody trusts it. Then it becomes decoration.

My rules:

  • Cap it at 50 active entries. Past that, you don’t have “memory,” you have a junk drawer.
  • Every entry needs a lastReviewed date. If it’s older than 90 days, it gets reviewed or archived.
  • Every entry needs an owner. If you can’t name an owner, it’s not important.

Add these fields to the template:

owner: "@team-platform"
lastReviewed: "2026-09-01"
expiresAfter: "2026-12-01"
Enter fullscreen mode Exit fullscreen mode

This is also where a simple linter helps.

A runnable linter to enforce hygiene

// scripts/lint-mistakes.mjs
import fs from 'node:fs';
import path from 'node:path';
import yaml from 'js-yaml';

const dir = path.join(process.cwd(), 'mistakes');
const files = fs.readdirSync(dir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));

let failed = false;

for (const f of files) {
  const raw = fs.readFileSync(path.join(dir, f), 'utf8');
  const doc = yaml.load(raw);

  const required = ['id', 'title', 'bannedPattern', 'preferredPattern', 'regressionTest', 'retrievalKeys'];
  for (const k of required) {
    if (!doc[k]) {
      console.error(`${f}: missing required field '${k}'`);
      failed = true;
    }
  }

  if ((doc.bannedPattern || []).length === 0) {
    console.error(`${f}: bannedPattern must list at least one concrete pattern`);
    failed = true;
  }
}

process.exit(failed ? 1 : 0);
Enter fullscreen mode Exit fullscreen mode

Hook it into CI:

  • run node scripts/lint-mistakes.mjs

Now your “memory” is a real artifact with quality gates.

One more thing: if you’re thinking about turning this into a bigger agent workflow, you’re already in agent framework territory. Treat memory like state. Version it. Test it.

The guardrails prompt I actually use (copy/paste)

This is the short prompt that makes assistants behave more like engineers.

Put it in your instruction file:

When asked to change code:

1) Identify impacted files and tests.
2) Retrieve relevant mistake memory entries (by path/test/error).
3) Propose a plan before editing if the diff will touch > 3 files.
4) After edits, run the regression test(s) listed in mistake memory.
5) Do not claim tests passed unless you ran them.
Enter fullscreen mode Exit fullscreen mode

That last line sounds petty. It prevents a lot of nonsense.

If you want to push this further, connect it to an eval gate. I’ve got a full playbook on AI engineering evals: regression gates for prompts, tools, RAG [2026].

A data point you can actually use: token overhead is real

If you’re worried that “all these rules and memory” will bloat context, you’re right.

Based on the measurement work I published on this site, OpenCode vs Claude Code token overhead had a 4.7x gap in one of my tests (OpenCode vs Claude Code Token Overhead: 4.7x Gap Tested [2026]). That’s not a moral judgement. It’s a budgeting reality.

This is why I push:

  • short, prioritized rules
  • path-specific scoping
  • patch-based retrieval over “dump the whole wiki”

If you’re managing LLM cost across a team, this matters. You can also sanity-check prices against the live tracker I maintain at kunalganglani.com/llm-prices.

Store instructions in the repo or tool settings?

Repo. Almost always.

Tool settings are fine for personal preferences like “prefer concise answers.” They’re terrible for team constraints like:

  • security rules
  • test commands
  • migration policies
  • “don’t touch this directory”

Repo-stored instructions are:

  • reviewable
  • diffable
  • enforceable
  • portable across tools

If you’re trying to standardize AI coding workflows across a team, read AI Coding Team Workflow Policy Guide [2026]: Stop the PR Flood. This is the organizational version of the same idea.

Prediction: coding assistants will ship “memory”, but teams will still lose

Every vendor is racing to ship sticky memory. It will help.

Most teams will still lose because they won’t do the unsexy part. Turn corrections into versioned artifacts and tests.

If you want to be ahead of that wave, pick one recurring relapse this week. Add one mistake memory entry. Add one regression test. Wire it to CI. Make the assistant earn your trust.

Assistants aren’t “forgetful.” They’re obedient to whatever you made durable. So make the right things durable.


Originally published on kunalganglani.com

Top comments (0)