Every .github/ file I add to a repo either loads on every Copilot request or only when the task calls for it. Mixing those two up is the main way Copilot chat gets slow and unfocused. My .claude/skills/ setup for this blog’s repo already runs on that principle, so when Copilot shipped Agent Skills as the on-demand counterpart to copilot-instructions.md, the question was which convention belonged in which bucket. See Configuring GitHub Copilot Custom Instructions for the always-on side’s setup mechanics.
Eager vs lazy loading
The distinction that actually matters isn’t “instructions vs skills” as separate features - it’s when each one enters context.
.github/copilot-instructions.mdandAGENTS.md- eager. Loaded into every single chat request, regardless of whether the current task needs them. Right for anything that should always be true: coding standards, the tech stack, review conventions..github/instructions/*.instructions.md- a middle case. Loaded automatically, but only when theapplyToglob matches the files in play (or via semantic matching if no glob is set). Scoped, not universal, but still automatic.- Agent Skills (
.github/skills/<skill-name>/SKILL.md) - lazy. Copilot reads only thenameanddescriptionfrontmatter of every skill up front - that part is cheap enough to have dozens of them sitting in the repo. The full body loads into context only when Copilot decides the current task matches the description, or when it’s called explicitly.
Every line in an eager-loaded file is a permanent tax on every request, whether it’s relevant or not. A skill costs nothing until the moment it’s needed.
Deciding which bucket something belongs in
- If it should shape every answer, it belongs in instructions.
- If it’s a specific, occasional workflow - generate a PR description, run an API-style review, scaffold a test file - it belongs in a skill instead.
A skill committed to .github/skills/ also reaches the whole team the moment they pull the repo - no separate setup step. That makes it the natural home for anything you keep re-explaining to Copilot chat by hand.
Structuring a SKILL.md
A skill is a folder, not a single file - SKILL.md plus whatever scripts or templates it needs alongside it:
.github/skills/
generate-pr-description/
SKILL.md
scaffold-test-file/
SKILL.md
template.test.ts
SKILL.md itself starts with frontmatter Copilot reads eagerly, then a body it only loads on match:
---
name: generate-pr-description
description: Draft a PR description from the current branch's diff and commit history. Use when asked to write, draft, or generate a pull request description.
---
Summarise the diff against the base branch into a PR description with a
Summary and Test plan section. Pull the actual commands or test names from
the diff - don't invent generic verification steps.
The description field is doing real work here - it’s the only part of the skill Copilot has to go on when deciding whether the current task matches. A vague one (“helps with PRs”) gets skipped when it should fire; an overly narrow one misses variants of the same task.
Notes
- The same eager/lazy question applies to Configuring Claude Code Memory - Claude Code’s own skills follow the identical pattern: cheap frontmatter loaded up front, full body loaded only on match.
- See Configuring GitHub Copilot Custom Instructions for
copilot-instructions.md, path-scopedinstructions/*.instructions.md, and whereAGENTS.mdcurrently fits.