How to Create a copilot-instructions.md File
If you write one Copilot configuration file, write this one.
.github/copilot-instructions.md is the only customization mechanism supported
on every Copilot surface — GitHub.com chat, all five IDEs, the CLI, the cloud
agent and code review. Everything else in this cluster is narrower.
It is also the highest-return twenty minutes available in this cluster, because it removes the corrections you are currently retyping several times a day — and it does so for everyone on the repository, not just for you.
Key takeaways
- Exact location:
.github/copilot-instructions.md, at the repository root’s.githubdirectory. Not the repository root itself. - Content is plain Markdown. No frontmatter is required.
- Write rules that are durable, specific and checkable. Anything task-shaped belongs in a prompt.
- Short beats comprehensive. Eight rules that get followed beat forty that get averaged.
- It is committed and reviewed like code, which is a feature: your conventions become a thing the team agrees to rather than a thing one person prefers.
Where the file goes
The path is exact and unforgiving. copilot-instructions.md at the repository root is not read;
neither is .github/instructions.md. It is .github/copilot-instructions.md.
mkdir -p .github
touch .github/copilot-instructions.mdStart by generating a draft
Rather than facing a blank file, let Copilot read the project. In the CLI:
copilot initOr /init from inside a session. It analyses the codebase using read-only tools
and writes the file, describing build and test commands, conventions, project
structure and stack.
What to put in it
Seven categories cover almost everything that is worth writing down.
Architecture. Where things belong. “Business logic lives in src/services/.
HTTP handlers stay thin.”
Environment and commands. How to run things. This is the highest-value category and the one most often missing — a model that invokes the wrong Python wastes a whole exchange on a problem that does not exist.
Testing. Which framework, what coverage is expected, what tests may not do.
Validation. What must happen before a change is considered complete.
Dependencies. Your policy, stated. Models reach for libraries.
Security. The rules that are non-negotiable, understanding that this is guidance rather than enforcement.
Documentation. What requires documenting and in what form.
A worked file
Here is a complete example for a Python service. It is deliberately short.
# Project instructions
## Architecture
- Business logic belongs in `src/services/`.
- HTTP handlers in `src/routes/` stay thin: parse, delegate, serialise.
- Do not put database queries in route handlers.
## Environment
- Python 3.12, virtual environment at `.venv`.
- Invoke tools as `.venv/bin/python -m <tool>`, never bare.
## Testing
- Use pytest.
- Every behaviour change needs a test covering the failure case, not
only the success case.
- Tests must not require network access.
- Do not modify existing tests to make a change pass. Fix the cause,
or stop and explain why the test is wrong.
## Dependencies
- Prefer the standard library.
- Do not add a dependency without explaining what it does that the
standard library cannot, and waiting for agreement.
## Validation
Before considering a change complete:
1. Run `.venv/bin/python -m pytest -q` and report the actual output.
2. Run `.venv/bin/python -m ruff check .` and fix what it reports.
3. Report the real output, not a summary of it.
## Security
- Never hard-code credentials. `.env` is local only and must not be
committed.
- Validate any input that reaches a query, a path, or a subprocess.Every rule there is checkable. None of them describes a quality, an aspiration, or a sprint.
Why this file specifically
It is worth being clear about why this one file gets a whole lesson when the custom instructions overview covers five mechanisms.
Universal support is the reason. Every other mechanism has gaps: personal
instructions miss VS Code chat, path-specific files miss GitHub.com chat and
Eclipse, AGENTS.md misses Visual Studio, prompt files reach three IDEs and
nothing else. This file is read by all nine surfaces.
For a team spread across editors — and most teams are — it is the only place a rule can be written once and be genuinely in force for everyone. That makes it the right home for the rules you actually care about, and it makes the other mechanisms refinements rather than alternatives.
The rule that makes the difference
Instructions describe durable project conventions, not tasks.
Good:
Use pytest for tests.
Run Ruff before considering Python changes complete.
Do not add dependencies without explaining why.Bad:
Fix issue #247 today.
Focus on the login bug this sprint.
We are migrating to the new API — prefer it.The third one is the interesting failure. It looks durable and is not: the migration will finish, and the instruction will still be there, quietly telling an agent to prefer something that is now the only option, or worse, something that was reverted.
Rules a model can act on
The difference between an instruction that changes behaviour and one that does not is usually whether it names something concrete.
Rules that name a command work: “run .venv/bin/python -m pytest -q”.
Rules that name a path work: “only modify files under src/”.
Rules that name a defined action when blocked work: “stop and explain rather than proceeding”.
Rules that name a quality do not: “write maintainable code”, “follow best practices”, “be careful with migrations”. These read well and give the model nothing to check itself against.
Converting a vague rule is usually a matter of asking what you would look at to know it had been followed. “Be careful with migrations” becomes “for any migration, state the expected lock duration on a table of a million rows” — same intent, and now it either happened or it did not.
Length
There is no published limit, and the useful test is behavioural: if the model follows the rules at the top of your file and drifts on the ones at the bottom, the file is too long.
Instructions occupy the same finite context as the code being discussed. A 2,000-word file is not free — it costs room on every request, forever, and under context pressure the details most likely to be summarised away are the specific rules you added because something went wrong.
Most repository files that actually work land between eight and twenty rules.
A demo repository to practise on
If you want to try this without touching a real project, three files are enough.
Put a deliberately opinionated rule in the file — something you can verify was followed or ignored:
# Project instructions
Every new function in `src/` must have a docstring in the following
form: a one-line summary, a blank line, then an `Args:` section.
Never use `print` for logging. Use the `logging` module.Then ask for a new function and see what comes back. Two things are worth noticing: whether the docstring form was followed exactly, and whether the logging rule was applied even though your prompt never mentioned logging.
The second is the point of instructions. A rule you did not restate was applied anyway, which is precisely the work this file does for you on every request from now on.
What not to put in it
Four categories reliably waste space.
Things the model already knows. It knows PEP 8, it knows what a REST verb is,
it knows how async works. Writing these down costs context on every request and
changes nothing.
Things a linter enforces. If Ruff already rejects it, the check is more reliable than the instruction and the instruction is now a second copy to keep in sync. Write instructions for what tools cannot check.
Things that change weekly. Current sprint priorities, in-flight migrations, who is on call. These will be wrong before anyone notices.
Aspirations. “Strive for elegance.” Nobody can check it, including the model.
What is left is the good stuff: your architecture, your commands, your history, your constraints — the things a new colleague would need told and could not read off the code.
Verifying it works
Commit the file, then ask:
Which instruction files are you currently following, and what do they tell you to do before completing a code task?
Do not change any files.
If it cannot summarise your rules, one of three things is true: the file is not being read, it is too long, or it is ambiguous. All three are worth knowing now rather than after you have relied on it.
Then test a rule directly. Ask for something your instructions forbid — adding a dependency, say — and see whether it pushes back or asks first. That is a more convincing check than the summary, because summarising a rule and acting on it are different things and only the second one matters.
Examples for other stacks
The Python example above is complete; here are the parts that change for other ecosystems, since the categories stay the same and the specifics do not.
Node / TypeScript. The environment section usually carries the package manager, because getting it wrong is a common and confusing failure:
## Environment
- pnpm, not npm or yarn. Lockfile is `pnpm-lock.yaml`.
- Run scripts with `pnpm run <script>`.
- TypeScript strict mode is on. Do not add `any` or `@ts-expect-error`
to silence an error — explain what the correct type would be.Go. The valuable rules tend to be about error handling and layout, because those are where conventions genuinely differ between teams:
## Conventions
- Wrap errors with context using `fmt.Errorf("...: %w", err)`.
- Never discard an error with `_`. If it genuinely cannot fail,
say why in a comment.
- Table-driven tests for anything with more than two cases.Infrastructure repositories. The rules that matter are about what must not be run, and this is one place where instructions and tool permissions should agree:
## Infrastructure
- This repository uses OpenTofu, not Terraform. Use `tofu`.
- Never run `apply` or `destroy` from a developer machine. CI applies.
- Run `tofu fmt -check` and `tofu validate` before considering a
change complete.
- Every resource needs deletion protection and a retention setting.
If one is deliberately omitted, say why in a comment.The last one is worth noticing: it asks for a justification in the code rather than for compliance, which produces something a human reviewer can evaluate.
Frequently asked questions
Does it need frontmatter? No. Plain Markdown. Path-specific instruction files
need applyTo frontmatter; this one does not.
Does the structure matter? Not to the parser. Headings help you and your colleagues maintain the file, which is reason enough to use them.
Will it apply to code completions? Instructions primarily shape chat and agentic interactions. Do not assume inline completions are governed by them.
Can I have both this and AGENTS.md? Yes, and both will be provided where the
surface supports them. Avoid contradicting yourself across the two — the model
receives both and reconciles them in a way you cannot predict.
What if a colleague disagrees with a rule? That is the healthy case, and it is why the file being committed matters. The fix is a pull request, not a local override, and the discussion happens once rather than every time someone is surprised.
Iterating
The best instruction files are written in response to observed failures rather than imagined ones.
When you correct Copilot twice for the same thing, that is the signal. The correction you just typed is the rule, and it is already phrased in a way that worked.
Three habits keep the file honest:
Update it in the same pull request as the change it describes. Migrating from
pip to uv means the file changes in that pull request. Treated as follow-up
work, it does not happen.
Read it when onboarding someone. A rule that needs explaining to a new colleague needs rewriting for the model too.
Prune it. Files grow monotonically unless someone removes things. After a year, a file nobody has cut is diluted enough that the important rules no longer stand out.
Measuring whether it helped
Instruction files are easy to write and easy to leave unexamined, which is how they become decoration. Two checks are worth running deliberately.
The A/B check. Rename the file, make a request you would normally make, and compare. If the output is indistinguishable, either your rules are things the model would have done anyway or they are not being followed. Both are worth knowing, and both argue for cutting.
The correction count. Keep rough track of what you correct Copilot about over a fortnight. Corrections that recur are missing rules. Corrections that stop recurring after you add a rule are the file working. A rule that never seems to prevent anything is a candidate for deletion.
Neither of these is rigorous, and neither needs to be. The failure mode they catch is the file that has been sitting there for a year with nobody able to say whether it does anything, which is common.
When to add more files
One repository file handles most projects. Two situations argue for more.
Rules that contradict by area. Test conventions and application conventions
frequently want different things. That is what
path-specific instructions
are for — .github/instructions/tests.instructions.md with an applyTo glob.
A monorepo. Several packages with different stacks cannot be described in one
file without most of it being irrelevant to any given task. AGENTS.md files,
where the nearest one to the work wins, suit this better.
Both are covered in the custom instructions lesson, along with which surfaces read them — worth checking before you rely on either, because the support is narrower than for this file.
Troubleshooting
Nothing seems to have changed. Confirm the path is exactly
.github/copilot-instructions.md, that it is committed, and that the surface you
are using reads it — all of them do, but confirm you are where you think you are.
Then ask the model to restate the rules.
Some rules are followed and others are not. Usually length. The rules that survive tend to be the ones near the top. Cut the file and see whether the stragglers start being applied.
A rule works for you and not for a colleague. Check whether they have personal instructions that contradict it. Personal instructions are invisible from inside the repository and are a common cause of “it works on my machine” for configuration.
It followed the rule but interpreted it oddly. The rule is ambiguous. Rewrite it naming a command, a path, or a specific action rather than an outcome.
It was following the rule and stopped. Long session, context pressure. In the
CLI, /context shows usage. Starting a fresh session restores the file to full
weight.
Next
Custom instructions explained covers the other mechanisms and the support matrix. Team coding standards is the capstone: turning a team’s human standards into files it owns and reviews.
If you work in the terminal, Cluster 5’s CLI custom instructions covers the same file from that surface’s perspective.
Put this into practice
7-minute exercise
Write an instructions file and prove it changed something
- Ask Copilot to write a small function and note how it handles errors.
- Create
.github/copilot-instructions.mdstating your project's error-handling convention in two sentences. - Reload the window so the file is picked up.
- Ask for the same function again.
How you know it worked The second version follows your stated convention. If it does not, the instruction was probably too abstract — name the exception type or the logging call you actually use.
Companion project Instruction file library
Check your understanding
3 questions. Nothing is scored or recorded, and you can retry as often as you like.
Sources
Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.
Your progress
Saved in this browser only. No account, no server, and nothing leaves your device. Clearing site data resets it.Sync across devices if you want it everywhere.
Saved in this browser and synced to your account, so it follows you between devices. Manage or delete it.
Was this lesson helpful?
We record which lesson you rated and whether it helped. Nothing identifies you — no account, no cookie, no session.