Make an AI Coding Agent Follow Your Code Style
If an AI coding agent keeps writing code that does not match your codebase, the fix is almost never a better instruction. It is a check the agent cannot argue with. Put your style rules in a formatter and a linter, wire those into the command the agent runs after every edit, and the agent...
If an AI coding agent keeps writing code that does not match your codebase, the fix is almost never a better instruction. It is a check the agent cannot argue with. Put your style rules in a formatter and a linter, wire those into the command the agent runs after every edit, and the agent converges on your style because non-conforming code fails in front of it. Prose in a config file is a suggestion. A failing exit code is a fact.
This matters more than it sounds. Style drift is not an aesthetic complaint, it is what makes an AI-assisted codebase gradually unreviewable: four naming conventions, three error-handling patterns, two ways of importing the same module, and no way to tell which was deliberate.
Why telling it your style does not stick
You can write a beautifully detailed style section in your instructions file and still watch the agent produce something else three files later. Three reasons, and none of them are the agent being careless.
The first is dilution. Your style rules compete for attention with the task description, the file contents, the test output and the conversation history. By the time an agent is deep in a multi-file change, a rule from twenty thousand tokens ago is one signal among thousands.
The second is that natural language rules are ambiguous in exactly the places where style matters. "Use descriptive variable names" does not resolve userRepo versus userRepository. "Prefer functional style" does not tell you whether a three-line for loop should become a reduce.
The third is that the training data disagrees with you. The agent has seen millions of files that use a different convention, and absent a strong local signal, that prior wins.
The hierarchy that actually works
Rank your enforcement mechanisms by how hard they are to ignore. Push every rule as far up this list as it will go.
Tier | Mechanism | Agent can ignore it? |
|---|---|---|
1 | Formatter that rewrites the file (Prettier, Black, gofmt, rustfmt) | No, the file is rewritten |
2 | Linter with a non-zero exit code in the agent's build command | No, the run fails |
3 | Type system, or a test that asserts structure | No, the run fails |
4 | Committed example code the agent reads before editing | Sometimes |
5 | A rule written in prose in your instructions file | Frequently |
Most teams start at tier 5 and stay there, then conclude the agent is bad at following instructions. The agent is fine. Tier 5 is a weak channel.
Step one: make the formatter non-optional
If a rule can be expressed as formatting, it should never appear in prose. Indentation, quote style, trailing commas, line width, import ordering, brace placement: all of it belongs to a tool.
Commit the config, do not rely on editor settings:
# Example for a TypeScript project. The principle transfers.
npm install -D prettier eslint @typescript-eslint/parser
# Commit .prettierrc and eslint.config.js at the repo root.
# Then make the check runnable in one command:
npm pkg set scripts.check="prettier --check . && eslint . && tsc --noEmit"The important part is that last line. One command, one exit code. An agent that can run npm run check gets a binary verdict on whether its work matches your project, and it can iterate against that verdict without asking you anything.
Step two: encode the rules a formatter cannot express
Formatters handle shape. They do not handle "use our Result type instead of throwing" or "never import from internal/ outside its own package" or "all database access goes through the repository layer".
Those become lint rules. Every mainstream linter supports custom or configurable rules, and this is what that capability is for. ESLint's no-restricted-imports and no-restricted-syntax cover most of what a team needs without writing a plugin:
// eslint.config.js, illustrative
export default [{
rules: {
// Architectural boundary, not a style preference
"no-restricted-imports": ["error", {
patterns: [{
group: ["**/internal/**"],
message: "Import from the package entrypoint, not internal/."
}]
}],
// Team convention with a real reason
"no-restricted-syntax": ["error", {
selector: "NewExpression[callee.name='Date']",
message: "Use clock.now() so tests can freeze time."
}]
}
}];Every rule you move from prose to a linter is a rule you stop repeating in every prompt. The message field matters, by the way: the agent reads it when the rule fires, so write the message as an instruction rather than a complaint.
Step three: tell the agent to run the check, in the file it always reads
Now the enforcement exists, the agent has to invoke it. This is the one thing that genuinely belongs in your instructions file, and it should be near the top and short:
## Before you finish
Run `npm run check` and fix everything it reports. Do not
report the task as done while it fails. If a rule seems wrong
for this change, say so and stop, do not disable the rule.That last sentence prevents the most common failure, which is an agent that satisfies the check by adding an ignore comment. If you have not written one of these files yet, how to write an agents.md file covers the rest of its contents.
Step four: give it a reference implementation
For the things that survive all of the above, patterns that are conventional rather than checkable, point at real code rather than describing it.
"Follow the pattern in src/services/billing.ts" outperforms three paragraphs describing that pattern, because the file contains all the details you would forget to mention: how errors propagate, what gets logged, where validation happens, how the tests are structured. Pick one exemplary file per layer and name them explicitly. Keep those files clean, because they are now documentation.
Step five: put the same check in CI
Local checks catch what the agent runs. CI catches what it skipped. Same command, so there is exactly one definition of "conforms":
# .github/workflows/check.yml, trimmed
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npm run checkRunning an agent against a repo where CI and local checks disagree is a reliable way to waste an afternoon. Running an AI coding agent in CI goes further into that setup.
What to do when it still drifts
Some drift survives all five steps. When it does, treat it as a bug report about your configuration rather than about the agent.
The same correction twice in a week means the rule is not encoded anywhere machine-checkable. Encode it.
Drift that appears only in long sessions is a context problem, not a style problem. Shorter tasks, fresh sessions.
Drift in a specific directory usually means that directory has no exemplar and no local config. Add both.
An agent that disables rules to make checks pass needs the explicit instruction above, and a CI job it cannot edit.
There is a related failure worth naming: an agent that reformats files it was not asked to touch, producing a diff where two lines of logic hide inside four hundred lines of restyling. That is the mirror image of this problem and stopping AI from changing code you did not ask it to deals with it directly. Run the formatter across the whole repository once, in its own commit, before you start.
Frequently asked questions
Do I need a separate instructions file per language? No, one file with per-directory sections works fine as long as each section names its own check command. Split only when the repo is genuinely multi-project.
Will strict linting slow the agent down? It adds seconds per iteration and removes minutes of review per change. The trade is heavily in your favour, with one exception: an extremely slow type check on a large repo can make iterative agent work painful. Split the fast checks from the slow ones and run the slow set only at the end.
What if my codebase has no consistent style today? Then the agent has no signal to follow and neither does anyone else. Pick a formatter, run it across everything in one commit, and start from there. This is a one-day job that pays for itself immediately.
Should I let the agent write the lint rules? For mechanical rules, yes, and review them. For architectural boundaries, write them yourself. The agent does not know which boundaries matter to you, and a wrong boundary rule is worse than none.
Does any of this help with correctness? Only indirectly. Style enforcement makes AI-written code reviewable, and reviewable code is where correctness problems get caught. Reviewing AI-generated code before you ship it is the other half of the job, and the broader landscape sits in our AI coding tools guide.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


