Using Custom Instructions with Copilot CLI

GitHub Copilot CLIAcademy lesson 60Cluster 5 · Lesson 9 of 12Intermediate15 min readVersion-sensitive
Published
Updated
Last technically verified
Using Custom Instructions with Copilot CLIGitHub Copilot CLI9Intermediate/github-copilot/cli/custom-instructions/

Every correction you type twice belongs in a file. “Use the virtual environment.” “Run the tests before you say you are done.” “Do not add dependencies.” Repeating these each session is not prompt engineering; it is a configuration problem with a configuration answer.

Copilot CLI reads instructions from several places, and the common mistake is assuming there is only one. .github/copilot-instructions.md is the best known and it is not the whole story — which matters, because an instruction file you forgot about is one of the most common explanations for behaviour you cannot account for.

Where instructions come from

Four mechanisms, with different jobs:

.github/copilot-instructions.md applies to everything in the repository. This is the main file and the one to write first.

.github/instructions/NAME.instructions.md applies to files matching a glob, declared in frontmatter. Subdirectories under .github/instructions are allowed.

AGENTS.md can live anywhere in the tree, and the nearest one to the file being worked on takes precedence. That makes it well suited to monorepos, where each package can carry its own conventions.

CLAUDE.md and GEMINI.md at the repository root are also recognised — a compatibility affordance so a repository already configured for another agent is not starting from nothing.

Start by generating one

Rather than writing from scratch, let the CLI read the project:

copilot init

Or /init from inside a session. It analyses the codebase using a restricted set of read-only tools and writes .github/copilot-instructions.md describing build and test commands, coding conventions, project structure and technology stack.

The generated file is a starting point, not a finished artefact. It is good at the factual parts — the commands, the layout, the stack — and it cannot know your preferences, because those are not in the code.

Writing a repository instruction file

The rules worth writing share a property: they are things you would otherwise say in every session.

# Project conventions

## Environment
- Python 3.12 in a virtual environment at `.venv`.
- Always invoke tools as `.venv/bin/python -m <tool>`, never bare.

## Before completing a task
- Run `.venv/bin/python -m pytest -q` and report the actual output.
- Run `.venv/bin/python -m ruff check .` and fix what it reports.

## Constraints
- Do not modify tests to make them pass. Fix the cause, or stop and explain.
- Do not add dependencies. If one seems necessary, explain why and wait.
- Do not change public function signatures without saying so explicitly.
- Never commit secrets. `.env` is local only.

Four properties make instructions like these work.

They are checkable. “Run pytest and report the output” either happened or did not. “Write high-quality code” cannot be evaluated by either of you.

They are specific to this project. Generic advice about good practice is already in the model. Your virtual environment path is not.

They state what to do when blocked. “Explain why and wait” gives the agent a defined action, which is what prevents it finding a creative route to the goal you did not want.

They are short. Every line competes with every other line for attention.

Path-scoped instructions

Rules that apply to only part of a repository belong in .github/instructions/, with an applyTo glob in frontmatter.

---
applyTo: "tests/**/*.py"
---

# Test conventions

- Use pytest, not unittest.
- Each test covers one behaviour and is named for that behaviour.
- Include the failure case, not only the success case.
- No network access. No real filesystem writes outside tmp_path.
- Tests must be deterministic: no wall-clock time, no randomness without a seed.

Multiple patterns go in a single quoted string, comma-separated:

---
applyTo: "**/*.ts,**/*.tsx"
---

An optional excludeAgent keyword excludes an instruction file from code-review or cloud-agent — useful when a rule is about how to write code rather than how to review it.

AGENTS.md and monorepos

AGENTS.md differs from the others in one important way: it can live anywhere, and the nearest file to the work takes precedence.

The root file carries what is true everywhere — the repository layout, the commit convention, the review process. Each package carries its own language, test runner and constraints. An agent working in services/api/ picks up the nearest file rather than an average of everything.

This solves a real problem. A monorepo with a Go service, a TypeScript frontend and a Python data pipeline cannot describe its conventions in one file without most of the content being irrelevant to any given task.

Personal instructions

Instructions that are about you rather than about a project belong at your user level, not in a repository. Preferences about explanation depth, or a standing instruction to always show the diff, are not things your colleagues should inherit.

GitHub documents the precedence as personal first, then repository, then organisation — with all relevant sets provided rather than one overriding another.

Seeing what actually loaded

Two commands answer this, and they are the first things to run when behaviour is inexplicable.

/instructions

Shows the instruction files in effect and lets you toggle them. Toggling is the fast way to test a hypothesis: disable a file, ask again, see whether the behaviour changes.

/env

The complete picture — instructions, MCP servers, skills, agents, hooks, plugins, LSPs, extensions. When an agent behaves in a way nothing in your repository explains, the cause is usually in this list.

copilot --no-custom-instructions

The blunt version: load nothing. If the strange behaviour disappears, it came from an instruction file, and /instructions will narrow down which.

What belongs in instructions, and what does not

A useful way to decide: instructions should carry information the agent cannot derive from the code, and rules it would otherwise violate by reasonable default.

Belongs in instructions. The commands to build, test and lint — because invoking them wrongly is the most common failure and the correct invocation is frequently not guessable. Decisions with a history: a linting rule you disabled deliberately, a library you rejected, a pattern you migrated away from. Boundaries: what must not change, what needs discussion first. And environment facts an agent cannot see — which distribution, which interpreter, whether SELinux is enforcing.

Does not belong. General language style the model already knows. Anything already enforced by a linter, since the linter is more reliable and the instruction is duplicated maintenance. Information that changes weekly, which will be stale before anyone notices. And anything that must be guaranteed — that is a permission flag.

Keeping instructions honest

Instruction files rot in a specific way: the project changes and the file does not, so it now describes something that was true. This is worse than having no file, because the agent follows it confidently.

Three habits keep them current.

Update instructions in the same pull request as the change. Migrating from pip to uv means the instruction file changes in that pull request, not later. Treating it as part of the change rather than follow-up work is the only version of this that actually happens.

Read them when onboarding someone. If a rule needs explaining to a new colleague, it needs rewriting for the agent too — the ambiguity is in the text, not the reader.

Notice when you correct the agent. A correction you type twice is either a missing instruction or a broken one. That is the signal to act on, and it arrives naturally in the course of work.

Instructions and the other customisation surfaces

Instructions are one of four mechanisms, and conflating them causes people to reach for the wrong one.

Instructions are prose guidance loaded into context. They shape behaviour across the whole session and cost context window.

Skills are reusable SKILL.md capabilities, discovered from .github/skills/, .agents/skills/, .claude/skills/, ~/.copilot/skills/ and ~/.agents/skills/. A skill is a packaged procedure for a specific kind of task rather than a standing rule.

Custom agents package a role with a restricted tool list. Where an instruction says “do not modify files”, an agent whose tools list omits edit cannot. That difference is the subject of the next lesson.

Hooks run at defined events, configured from .github/hooks/*.json. They execute rather than advise, which makes them the enforcement mechanism of the four — and also the one to review most carefully in someone else’s repository.

The rule of thumb: instructions for “usually do it this way”, agents for “this role has these capabilities”, hooks for “this must happen every time”, skills for “here is how to do this particular task”.

Instruction files are a security surface

An instruction file changes how an agent behaves. That is the feature, and it is also why one arriving in a pull request deserves review.

Consider what a malicious AGENTS.md could attempt: instructing the agent to include a particular dependency, to skip a validation step, to treat a directory as trusted, or to send data somewhere as part of “normal” workflow. None of that is exotic; it is the intended mechanism used against you.

That last point is the practical one, and the GitHub Actions lesson develops it: a workflow triggered by a fork is running against a repository state the contributor controls, including any instruction files in it.

A worked example

Build the configuration for a small project.

Generate the base:

copilot init

Then edit it down to what matters, add a path-scoped file for tests, and verify:

Copilot prompt

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.

Asking the agent to restate the rules is a genuinely useful check. If it cannot summarise them, they are not loaded, they are too long, or they are ambiguous — and all three are worth knowing before you rely on them.

Instructions across a team

A single developer’s instruction file is a convenience. A team’s is closer to shared infrastructure, and it behaves differently.

The main shift is that instructions become a place where conventions get decided rather than merely recorded. “Do not add dependencies without discussion” written into a repository file is a policy, and a colleague encountering it through an agent’s refusal is encountering it for the first time. That is worth being deliberate about — the file should say things the team has agreed, not things one person prefers.

The practical split is clean. Repository files carry team decisions and go through review like any other change. Personal instructions carry individual preferences about explanation style or output verbosity, and stay out of the repository entirely. When someone finds a repository rule wrong, the fix is a pull request, not a local override — which keeps the file matching reality.

Making rules the agent can act on

The difference between an instruction that changes behaviour and one that does not usually comes down to whether it names something concrete.

A rule that names a command works: “run .venv/bin/python -m pytest -q”. A rule that names a path works: “only modify files under src/”. A rule that names a defined action for a blocked case works: “stop and explain rather than proceeding”.

Rules that name a quality do not: “write maintainable code”, “be careful with migrations”, “follow best practices”. These are not wrong, they are simply not actionable — there is no observation the agent can make that tells it whether it complied.

The test is whether you could check compliance yourself by looking at the output. If you cannot, neither can the model.

Common mistakes

Writing an essay. Long instruction files dilute. Eight rules that get followed beat forty that get averaged.

Vague rules. “Write clean code” is unfalsifiable. “Functions under 50 lines” can be checked.

Duplicating the model’s general knowledge. It knows PEP 8. It does not know that your team rejected a linting rule for a specific reason — write that.

Forgetting the file exists. Six months later, instructions describe a framework you migrated away from. /instructions is how you find out, usually after spending an hour wondering why the agent keeps suggesting something nobody has used since spring.

Using instructions where permissions belong. “Never run terraform apply” in an instruction file is a hope. --deny-tool='shell(terraform apply:*)' is a guarantee.

Contradicting yourself across scopes. A repository file and a path-scoped file that disagree produce unpredictable behaviour, because both are supplied.

What to carry forward

Generate with copilot init, then cut it down.

Write rules that are checkable, specific, and short.

Use path-scoped files when conventions differ by area.

Use AGENTS.md nesting for monorepos.

Run /instructions and /env first when behaviour is unexplained.

Review instruction files as code, especially from outside contributors.

Use permissions for anything that must not happen. An instruction is advice the model usually takes; a denied tool is a rule it cannot decline.

A last framing that helps when deciding whether something belongs in a file at all. Instructions are the answer to “what would I tell a competent new colleague on their first day that they could not work out from reading the code”. That excludes language basics, which they know, and it excludes anything obvious from the repository, which they will read. What remains is the history, the constraints, and the commands — which is exactly the right content for the file.

Instructions and the context window

One practical constraint that shapes how long an instruction file should be: instructions occupy context. Every rule competes with the code the agent needs to read, and on a long session they compete with the conversation too.

A short, sharp file costs little and survives. A long one costs real room and, as /compact summarises history over a long session, the details most likely to be lost are exactly the specific rules you cared about.

/context shows the pressure. If a session is running close to full and the agent has started missing instructions it followed earlier, the answer is usually to compact deliberately or start fresh — not to restate the rule more forcefully.

Next

Custom agents is the natural progression: where instructions shape every session, an agent packages a role together with a restricted tool list, making the constraint structural rather than advisory.

The commands cheat sheet lists every configuration location the CLI reads, and the pillar covers how instructions relate to skills, MCP servers and plugins.

Sources

Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.

Primary sources

Additional references