# How to Prevent AI Coding Assistant Repeating Mistakes [2026]

> Your assistant isn’t “forgetful.” Your workflow is. Here’s a repo-first system: mistake memory, patch-based retrieval, and regression gates that stop relapses.

- Canonical: https://www.kunalganglani.com/blog/prevent-ai-coding-assistant-mistakes
- Author: Kunal Ganglani
- Published: 2026-09-19 · Updated: 2026-09-19
- Category: Developer Tools · Tags: ai-coding, context-engineering, agent-memory, workflow, developer-tools

## TL;DR

AI coding assistants keep “fixing” a bug and then bringing it back because the fix never becomes a durable part of your project. The solution is a repo-first workflow: write short instruction files next to the code, record each recurring mistake in a small template, and add a regression test that fails if the mistake returns. Then wire a simple script that automatically pulls the most relevant past fix (the exact commit or diff) whenever related files or tests change. The takeaway: don’t rely on chat memory. Make the assistant’s learning real by turning it into versioned rules, patches, and tests.

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](/blog/github-copilot-vs-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.

![computer coding screengrab](https://cdn.sanity.io/images/vzekdneq/production/cc1be59fec7311dca16db07b7c5015966f10eddd-1200x675.webp)

1. **Write the mistake down in-repo** (a single structured entry). Not in tool settings.
1. **Pin the banned pattern** (what to stop doing) and the preferred pattern (what to do instead).
1. **Attach a regression test** that fails if the mistake comes back.
1. **Store the patch**: link the commit or save the diff that fixed it.
1. **Add a short guardrails prompt** the assistant must follow before proposing changes.
1. **Make instructions scoped**: repo-wide for global rules, path-specific for local rules.
1. **Budget your rules**: top 10 rules win. Everything else gets pruned or moved to docs.
1. **Automate retrieval**: when files/tests/errors match, inject the relevant mistake entry + patch.
1. **Gate on CI**: the same test that caught the bug blocks PRs.
1. **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.

![MacBook Pro with images of computer language codes](https://cdn.sanity.io/images/vzekdneq/production/aa92fb59a462ca663d9ffbfac702b83dceeadfd3-1200x675.webp)

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](https://docs.anthropic.com/en/docs/claude-code/memory)).

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.

![Code appears on a screen](https://cdn.sanity.io/images/vzekdneq/production/6cd96ffd98e55edfa96c3658379036d6660575b9-1200x675.webp)

### 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](https://platform.openai.com/docs/guides/prompt-engineering)).

“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](https://arxiv.org/abs/2308.00352)).

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](https://docs.anthropic.com/en/docs/claude-code/memory))
- GitHub Copilot: `copilot-instructions.md` with repo-wide and path-specific instructions ([GitHub](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot))
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):

```md
# 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”.
```

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](/pillars/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`

```yaml
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."
```

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](/blog/rise-of-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](/glossary/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`

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

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.

```js
// 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`);
}
```

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.
1. Assistant adds regression test.
1. CI runs the regression suite.
1. 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`

```yaml
repos:
  - repo: local
    hooks:
      - id: regression-tests
        name: run regression tests
        entry: bash -c 'pnpm test tests/regression'
        language: system
        pass_filenames: false
```

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

### CI example (GitHub Actions)

`.github/workflows/ci.yml`

```yaml
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
```

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]](/blog/ai-coding-agent-harness-overhead-measurement). 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](/pillars/ai-security-safety) 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.
1. **Conflicting rules.** Two instruction files say opposite things. The model flips a coin.
1. **Rules are too long.** They get truncated or compacted.
1. **Your rule is non-actionable.** “Write clean code” means nothing.
1. **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:

```md
Before proposing changes, restate the relevant guardrails you are following (max 5 bullets).
```

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](/blog/prompt-injection-2026-owasp-llm-vulnerability) and [LLM security](/pillars/ai-security-safety). 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:

```yaml
owner: "@team-platform"
lastReviewed: "2026-09-01"
expiresAfter: "2026-12-01"
```

This is also where a simple linter helps.

### A runnable linter to enforce hygiene

```js
// 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);
```

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:

```md
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.
```

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]](/blog/ai-engineering-evals-gates).

## 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]](/blog/opencode-vs-claude-code-token-overhead)). 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](/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](/blog/ai-coding-team-workflow-policy-guide-2026). 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.

Photo by Danial Igdery on Unsplash.

## FAQ

### How do I stop ChatGPT/Copilot from repeating the same mistake?

Stop trying to “remind” it in chat. Put the constraint in your repo (instruction file), add a regression test that fails if the mistake returns, and store a short “mistake memory” entry with the banned pattern and preferred pattern. Then have your workflow retrieve that entry automatically based on file paths or failing test names.

### How do you give an AI assistant persistent project context?

Use repo-stored instruction files (like CLAUDE.md or copilot-instructions.md) so the context lives with the code and survives new sessions. Keep the rules short, scoped to the right directories, and versioned in Git. For recurring failures, add a separate mistake memory file plus a regression test so the “context” is enforced, not just described.

### How do I debug when my AI assistant isn’t following my instructions?

First confirm the instruction file is actually being loaded in the current scope (repo root vs subdirectory). Next look for conflicting rules across multiple files, or rules so long they get truncated or compacted. Finally, force the assistant to restate the top guardrails it’s following before it edits anything. If it can’t restate them, it never really had them.
