AI coding assistants — Codex, Claude Code, Cursor, GitHub Copilot and similar tools — no longer just autocomplete a line. They can read your whole repository, touch multiple files, run builds, execute tests, and "fix" things on their own initiative.
That's exactly why a small, well-scoped bug can quietly turn into a bigger one:
Small bug → incomplete context → AI guesses → changes multiple files
→ original issue still not fixed → new problems appear
→ more debugging, more AI usage, less trust in the output
The fix isn't giving the AI less power or more power — it's giving it the right knowledge, one focused task, and clear boundaries. That's what this post is about.
1. Set Up Your Project So AI Can Actually Understand It
A prompt like "fix the login issue" leaves the AI to guess your framework, your architecture, your database, which packages are approved, and what it's not allowed to touch. Sometimes it guesses right. Sometimes it confidently guesses wrong. The fix is to keep that knowledge in the repo instead of re-explaining it every time.
README.md — still written for humans first, but it is also a useful source of project context when an AI assistant inspects your repository. Keep it accurate and current:
# My Application
ASP.NET Core 8 MVC · EF Core 8 · MySQL 8
Build: dotnet build
Tests: dotnet test
Beyond the README, coding tools that support persistent project instructions use specific files or rule locations. The content serves a similar purpose — conventions, commands and boundaries — but the filename and behaviour differ by tool:
| Tool | Project instructions | Typical location |
|---|---|---|
| Codex | AGENTS.md |
Repository root or relevant nested directory |
| Claude Code | CLAUDE.md |
Repository root |
| Cursor | AGENTS.md or project rules |
Repository root or .cursor/rules/*.mdc |
| GitHub Copilot | copilot-instructions.md |
.github/copilot-instructions.md |
These tools evolve, so check their official documentation before adopting a file structure: Codex, Claude Code, Cursor and GitHub Copilot.
You do not need to maintain four large versions of the same document. Keep one canonical source of shared project knowledge wherever practical. Use imports or references when a tool supports them; otherwise, keep its instruction file short and point it toward the same detailed documentation. The important thing is that the instructions agree, not that every tool has its own wording.
Whichever file(s) apply to your toolchain, keep it short and specific:
# Development Rules
- Use ASP.NET Core 8; follow Controller → Service → DbContext architecture.
- Use async/await for database operations. Store dates in UTC.
- Do not introduce Repository or UnitOfWork patterns.
# Validation
- Run `dotnet build` after code changes.
- Run relevant tests before considering a task complete.
# Guardrails
- No database schema changes without approval.
- No new packages without approval.
- No changes to authentication/authorization without approval.
- No unrelated refactoring. No secrets or credentials in source code.
Cursor rules deserve a special mention: instead of one giant global file, Cursor supports rules scoped to a path or file type. For example, API-specific instructions can apply only to files inside the API project instead of being loaded during frontend work. This is more focused than applying every rule globally, and it is a useful pattern in any tool that supports scoped instructions.
Supporting docs — architecture decisions, database schema notes, security requirements. Don't inline these into your main instructions file; link to them so they're pulled in only when the task needs them:
/project
├── README.md
├── AGENTS.md
├── CLAUDE.md
├── docs/
│ ├── architecture.md
│ ├── database.md
│ └── security.md
├── .cursor/rules/
├── src/
└── tests/
For database changes, read docs/database.md.
For auth work, read docs/security.md.
A large permanent instructions file is not automatically better. Instructions that are loaded for every task consume context, while important rules can become buried ("never touch authentication without approval" should not be rule 340 of 400). Large files also become stale as the codebase evolves and can eventually contradict the implementation. Keep permanent instructions short, current and limited to what is normally relevant; let detailed documentation carry the rest.
2. Put Guardrails in Place Before You Give AI Any Autonomy
Guardrails aren't a style preference — they're what stands between "the AI made a scoped fix" and "the AI touched your auth flow because it seemed related." Define these upfront, not after something goes wrong.
Normally fine to do on its own:
✓ Read/search code, analyze existing logic, suggest solutions
✓ Modify clearly scoped application code
✓ Run local builds and tests, format code
Should propose first, then wait for approval:
⚠ Database schema changes or new migrations
⚠ Adding/upgrading packages
⚠ Changing authentication or authorization
⚠ Changing public APIs, large refactors, new architecture patterns
Never do without explicit, separate authorization:
✗ Destructive database commands or touching production data
✗ Exposing credentials/secrets, hard-coded API keys
✗ Disabling security checks
✗ Deleting or weakening tests just to make a build pass
✗ Broad, unrelated mass changes
An instructions file is guidance for the model, not a security control — for genuinely dangerous operations (prod data, secrets, deploy access), back it with real permissions, not just a written rule.
3. Persistent Context vs. Task Context
Not everything belongs at the same altitude. Persistent facts belong in your project files; task-specific facts belong in the prompt.
| Persistent context (project instructions) | Task-specific context (current prompt) |
|---|---|
| Framework, version, database and architecture | Current bug or feature |
| Coding conventions and build/test commands | Relevant module or files |
| Standing security and change guardrails | Constraints and acceptance criteria for this change |
Compare:
Fix password reset.
against:
Issue: Password reset fails after token validation.
Expected: A valid token should let the user set a new password.
Investigate: AccountController, ResetPasswordService.
Constraints: no schema changes, no auth redesign — analyze root
cause first, make the minimum change, then build and test.
The second prompt is longer, but it can save AI usage overall. It reduces wrong guesses, repeated explanations and unnecessary changes — the things that waste far more context than a few extra prompt lines.
4. Manage Context and Tokens Like a Budget
A short visible prompt doesn't mean small context usage. Behind it, the assistant may be carrying your prompt, conversation history, project instructions, relevant source files, git diffs, terminal output, build errors, test results, and prior tool calls — all at once.
Some AI systems use caching to process repeated input more efficiently. Depending on the product and plan, this may reduce cost or latency. However, caching does not make irrelevant information useful. Cached context can still influence the model's decisions, so the target should always be:
useful permanent context + relevant files + current task
not the entire repo, every doc, and hours of unrelated history.
Some coding assistants also use compaction, where older conversation history is summarized as a session becomes long. Compaction helps the work continue, but it is a safety net, not project documentation. If a decision matters later, write it into architecture.md or the appropriate project instruction file. Do not assume that every detail from a three-hour conversation will remain equally visible after summarization.
5. Why Long Sessions Degrade — And When to Start Fresh
Early in a session, context is small and focused. Hours in, it's carrying the original task, every file touched since, prior patches, build errors, rejected approaches, and tangents that never got closed out. Even with caching and compaction, the model has more to reconcile, and stale assumptions can resurface as if they were still current.
- Same problem or feature → keep the session going. (Diagnose → fix → build → test, all in one thread.)
- Different, unrelated problem → start a new session. Don't let a login bug, a CSS tweak, and a payments issue pile into the same thread — a fresh session starts with a clean, relevant context instead of dragging three histories along.
If a session starts repeating itself, forgetting instructions you gave it, or making odd assumptions — that's the signal to restart, not push through.
6. Break Work Into Small, Verifiable Tasks
"Build the customer management module" invites changes across the database, services, controllers, APIs, UI, and tests all at once — more than anyone can realistically review in one pass. Instead:
1. Understand the existing module.
2. Identify affected files.
3. Propose the approach — don't code yet.
4. Flag any database/security impact.
5. Implement one scoped part.
6. Build. Test. Review. Then continue.
For anything unfamiliar or a live bug, ask for analysis before code:
Analyze this issue first — don't modify anything yet.
Identify: likely root cause, files involved, assumptions,
proposed fix, and possible side effects.
Once you've reviewed that, approve the implementation explicitly and ask for the minimum change, not a rewrite.
Rule of thumb: never let the assistant generate more code than you can actually review. It writes faster than you can verify — task size is your main lever for keeping that in balance.
7. Hallucinations Are Often Subtle, Not Obvious
AI-generated bugs don't usually look like bugs. The assistant can confidently call a method that doesn't exist, suggest the wrong package, use an API from a different framework version, assume a database column is there, invent a config key, or solve a slightly different problem than the one you asked about — and the code can still look completely professional.
Treat every output as a proposal, not a result:
AI output → proposal → build → test → review → accepted code
"Done, everything works" from the assistant is not verification. A green build and passing tests are the minimum bar, not proof that the business requirement is correct. Review the diff and run or write a test that targets the behaviour you asked to change.
8. The Workflow, End to End
Combine all of the above into one repeatable loop:
Understand → Context → Guardrails → Analyze → Plan
→ Small Change → Build/Test → Review
- Understand the outcome you need before asking the AI to change code.
- Context — point it at exactly the files/docs this task needs.
- Guardrails — state what it may not touch and what needs explicit approval.
- Analyze — have it explain what it found before writing code.
- Plan — get a short plan or diff outline; approve or correct it.
- Small Change — implement one verifiable unit of work.
- Build/Test — after each meaningful scoped change, not after one huge batch.
- Review — read the actual diff against the plan and the scope you gave it.
This may feel slightly slower than saying "just fix it," but it is usually faster overall. It reduces the expensive failure mode of discovering several steps later that an early assumption, API or database field never existed.
Final Checklist
Before accepting an AI-assisted change:
- Did it understand the actual requirement?
- Did it use the correct framework and version?
- Did it modify only the necessary files?
- Did it add or upgrade any dependency?
- Did it invent an API, method, field or configuration?
- Did database behaviour or schema change?
- Did authentication or authorization change?
- Are secrets and credentials protected?
- Did it introduce unrelated refactoring?
- Does the project build successfully?
- Do the relevant tests pass?
- Did I read the diff myself?
Final Thoughts
The temptation with these tools is to feed them more — more files, more history, more autonomy. What actually works better is more discipline: keep permanent instructions short, push detail into structured docs, hand over one focused task at a time, start fresh when the subject changes, analyze before coding, and put guardrails in place before autonomy — not after something breaks.
AI should accelerate your engineering judgment, not replace it. The goal isn't the maximum amount of generated code — it's the correct solution, reached with fewer wrong turns, less wasted context, and fewer new problems created along the way.
