Three files, one request
Copilot's instructions are not a file, they are a stack. Three things in a repository can end up in the same chat request, and they combine rather than override each other, which is a friendlier rule than the one Claude Code applies and a more expensive one, because everything that loads is spending context.
| What you write | When it loads |
|---|---|
.github/copilot-instructions.md | Every Copilot Chat request in that repository |
.github/instructions/*.instructions.md | Only when its applyTo glob matches the files in play |
AGENTS.md | Read by the Copilot coding agent and by recent editors, nearest file in the tree |
Two more layers live outside the repository. Personal instructions follow your account across every repository you open, and organisation instructions are set once for everyone. Neither is in version control, which is worth remembering the next time a colleague cannot reproduce what your Copilot did.
The practical consequence of the stack is that your repository file is never the whole prompt. It is one voice among several, and the way to keep it heard is to make it short and specific rather than thorough.
The applyTo line decides everything
A path-scoped instructions file is markdown with a frontmatter block on top. The frontmatter is the whole feature:
---
applyTo: "src/api/**/*.ts"
description: "Route handlers and their validation"
---
- Every handler validates its body with a zod schema before touching the
database. No manual type assertions on request input.
- Errors return the shared `ApiError` envelope. Never a bare string.
- New routes get a test in the sibling `__tests__` directory that covers the
rejection path, not just the happy one.Without applyTo the file has no scope and does nothing useful. With applyTo: "**" it behaves like the repository-wide file, which is occasionally what you want and more often a mistake made by copying an example.
The mistake this format exists to prevent is the one most repository-wide files make: a paragraph about API validation sitting in a file that loads when someone asks about a stylesheet. It is not wrong, it is just never relevant, and irrelevant instructions are how a model learns that this file is mostly noise.
"**/*.tsx"for component conventions, not"src/**", which catches the server too."**/*.test.ts"for the testing rules, so they are absent when nobody is writing a test.- One file per concern, named after the concern.
api.instructions.mdandtesting.instructions.mdbeat onerules.instructions.mdthat grows forever.
What belongs in the repository-wide file
Whatever is true no matter which file is open. In practice that is the build, the test command, and the two or three conventions a new contributor gets wrong on their first day:
# Contributing with Copilot
This is a pnpm monorepo. `pnpm dev` runs everything, `pnpm test` is vitest,
`pnpm typecheck` is not optional and fails separately from the tests.
## Conventions
- TypeScript strict. No `any`, no non-null `!`.
- Named exports only. A default export is a review comment.
- Dates are stored as ISO strings in UTC and formatted at the edge.
## Before you say a change is done
- `pnpm test` and `pnpm typecheck` both pass.
- Any new public function has a doc comment saying why it exists, not what
it does.That is roughly the ceiling. Everything past it competes with the code the model is being shown, and the argument for a hard budget is worked through in the CLAUDE.md post: the second page of an instruction file is not read twice as carefully as the first.
Prompt files do a different job
A prompt file lives in .github/prompts/, ends in .prompt.md, and is invoked deliberately by typing a slash command in chat. It is not loaded on every request:
---
mode: agent
description: "Draft release notes from the commits since the last tag"
---
Read `git log $(git describe --tags --abbrev=0)..HEAD`.
Group the commits under Added, Fixed and Changed. One line each, written for
someone who uses the product and has never read the codebase. Skip commits
that only touch tests or CI. End with the upgrade steps, or "No action
needed" if there are none.This is the release valve for an instruction file that has grown past reading. Anything you need occasionally and precisely is a prompt file. Anything you need on every single request is an instruction. Sorting an overgrown file into those two piles usually halves it, and the half that stays gets followed more closely for having fewer neighbours.
Where it quietly does nothing
Every one of these failures renders as a normal session in which Copilot simply does not do what you wrote:
- The file is in the wrong place.
.github/copilot-instructions.mdis the path, not.github/COPILOT.mdand not acopilot-instructions.mdat the repository root. - A path-scoped file has no
applyTo, or has one whose glob does not match the file you are actually editing. - The instructions setting is off in the client, or the client is an older one that never supported the format.
- The instruction asks for something outside the model's reach, such as fetching a URL for context on every request.
- Two files disagree. The repository-wide file says named exports only, a path-scoped file shows a default export in its example, and you get a coin flip.
The last two are the interesting ones, because they are content problems rather than plumbing problems, and they are the subject of the post on why a model ignores instructions.
Read the References, not the file
Copilot tells you what it used. A chat response carries a references list naming the instruction files applied to it, and expanding that list is a two-second check that settles the question no amount of rereading your own markdown can.
Do it before you rewrite a rule you believe is being ignored. If the file is not in the list, the wording was never the problem and editing it is time spent on the wrong file. If the file is in the list and the rule still did not happen, the rule is probably not checkable, which is a fixable and much more common failure than it sounds.
The repair is the same one that works on any prompt: say what the constraint is in a form you could verify, and then check the wording against the desk before you commit it to a file every request has to carry.