GitHub Copilot (copilot)
Set up this tool
GitHub Copilot reads most project configuration from .github/, and VS Code reads MCP servers from .vscode/mcp.json.
First rule
Install the client using the official setup guide, then open your project in it.
Select copilot in agnostic-ai.yaml, then follow First rule to add the shared commit-prefix rule and sync it. With the default paths, it reaches .github/instructions/conventional-commits.instructions.md.
VS Code, Copilot CLI, and the cloud agent read different settings and MCP files. The reference below names the surface for each output.
For existing files, check what import copilot reads before syncing.
Verify
Run agnostic-ai sync --check and inspect .github/instructions/conventional-commits.instructions.md for the commit-prefix rule. Open the project in VS Code with Copilot. Ask for a commit subject for a small fix; it should use fix:.
Next, add a skill.
Output
.github/copilot-instructions.md # entry-point file with the AGNOSTIC_AI.md body
.github/instructions/<name>.instructions.md # scoped rule per file
.github/agents/<name>.agent.md # one custom-agent profile per agent
.github/skills/<name>/SKILL.md # one folder per skill, bundled assets included
.vscode/mcp.json # when MCP entries exist; VS Code's file (deprecated in 1.140), servers key
.github/mcp.json # when MCP entries exist; Copilot CLI's file, mcpServers key
.mcp.json # only with outputs.copilot.root-mcp-file; mcpServers key
.github/hooks/agnostic-ai.json # when hook entries exist
.github/hooks/scripts/agnostic-ai-portable-hook.sh # when a portable before-tool hook exists
.github/copilot/settings.json # when a Settings model or a disabled MCP server exists
.vscode/settings.json # chat.useAgentsMdFile: false, when another target writes AGENTS.md
Native mappings
Field mappings, discovery, and compatibility
Instructions
.github/copilot-instructions.md holds the whole AGNOSTIC_AI.md body, even when another target writes a root AGENTS.md. Copilot Chat on GitHub.com, Visual Studio, and JetBrains, and code review in the IDEs, read that file and not AGENTS.md (support by surface).
- VS Code chat, the cloud agent, and GitHub.com code review can also read
AGENTS.md, so the shared body can load twice. Sync setschat.useAgentsMdFile: falsein.vscode/settings.jsonfor VS Code's local agent in a trusted workspace. This also disables nestedAGENTS.md; native.instructions.mdfiles carry those rules instead. - Your explicit setting wins. A true value gets a double-load note. Removing Copilot or the last
AGENTS.mdwriter removes the generated setting. Cloud and code-review surfaces have no such switch. See support by surface.
Rules
A rule with globs or a folder scope (like rules/backend/auth.md) gets its own .instructions.md file with applyTo.
- Scope and patterns combine:
scope: src/awithglobs: tests/a/**writesapplyTo: src/a/**,tests/a/**. - An always-on rule with no scope (no globs, or
alwaysApply: true) getsapplyTo: "**". - A rule with
alwaysApply: falseand no scope or globs gets noapplyTo. VS Code loads it when itsdescriptionmatches the task (custom instructions).import copilotreads it back the same way. descriptionand everyx-copilotkey go next toapplyTo.
Agents
custom agent profiles at .github/agents/<name>.agent.md. An existing <name>.md keeps its path. VS Code reads any .md there, so a second file would load twice.
- Frontmatter has
name,description, andtoolsandmodelwhen set. x-copilot.namesets a display name that differs from the file name. Import fills it when the two differ.- Other
x-copilotkeys (target,user-invocable,mcp-servers,effort, ...) are copied as written. - The profile has no effort key, so a portable
effortgets a sync note. Per-agenteffortLevelexists only in thesubagents.agentssetting of your personal~/.copilot/settings.json, whichsync --globalwrites (see global output).
Skills
Copilot skills at .github/skills/<name>/SKILL.md. Copilot also reads .claude/skills/ and .agents/skills/. A skill found there and not in .github/skills/ is written in place. outputs.copilot.skills-dir sends every skill to one directory.
SKILL.md keeps argument-hint and disable-model-invocation, unless another target writes the same skills folder (agent skills). Copilot has no commands, so each command becomes a skill there that only the user starts from the / menu (commands).
Old copies
Sync removes old agent-<name>.instructions.md and skill-<name>.instructions.md files.
Chat modes
With outputs.copilot.chatmodes-dir set, each agent is also written as a VS Code Custom Chat Mode at <dir>/<name>.chatmode.md with description, model, and tools frontmatter.
Settings
.github/copilot/settings.json keeps its other keys and gets:
- the last portable
model, as the Copilot CLI repository default. - a portable
effortoflow,medium,high, orxhigh, aseffortLevel. Other values get a sync note. - a settings spec's
x-copilotblock, for keys agnostic-ai has no field for, such asrespectGitignore. x-copilot.enabledPluginsandx-copilot.extraKnownMarketplaces, the repository's default plugins and plugin marketplaces. Copilot CLI and Copilot in the cloud both read them. Repository values override personal ones forenabledPlugins; a marketplace'sautoUpdatehas no effect at this level (CLI config reference).x-copilot.deniedUrls, Copilot's blocked URL list. No portable rule sets it.allowedUrlsworks only in your personal settings.- every MCP spec with
disabled: true, sorted, indisabledMcpServers. It is the only file setting that stops a configured server from starting (CLI config reference). import copilotrestores the model tosettings/copilot.yamlandeffortLevelaseffort(orx-copilot.effortLevelwhen another settings spec sets a different effort), and keepsenabledPluginsandextraKnownMarketplacesunchanged underx-copilot. Other keys only Copilot uses stay in the native file.
MCP
VS Code and Copilot CLI read different files, so sync writes both. Override them with outputs.copilot.mcp-file and outputs.copilot.cli-mcp-file.
.vscode/mcp.jsonuses the VS Code schema: top-levelservers, each with atype(stdio,http, orsse). VS Code passes it to Copilot harness sessions, except servers that need interactive input (VS Code MCP servers). Sync ownsserversand keeps other top-level keys, such asinputsandsandbox. It strips JSONC comments and reformats the file. Invalid JSONC stops the write..github/mcp.jsoncarries the same servers undermcpServers, since Copilot CLI rejects theserverskey (Copilot CLI MCP docs). Sync owns the whole file, and the whole root copy.A per-server
toolslist of allowed tools (default*) goes only to.github/mcp.jsonand the root copy. VS Code documents no such key, and Codex'stoolsmeans something else.A
rootslist is copied as written. Neither VS Code nor GitHub documents it per server.Only
.vscode/mcp.jsongets the VS Code fields (VS Code MCP configuration). Set them at the spec's top level or underx-copilot;x-copilotwins:- stdio servers:
cwd,envFile(for example${workspaceFolder}/.env), andsandboxEnabled(macOS and Linux only). - stdio servers:
sandboxwithfilesystem.allowWrite,filesystem.denyRead,filesystem.denyWrite,network.allowedDomains, andnetwork.deniedDomains, each a list. VS Code reads allow rules only here, not in the top-levelsandbox. Other keys are dropped. - remote servers:
oauth: {clientId, enterpriseManaged}. - all servers:
dev.watch, a glob or list of globs that restarts the server on change. - stdio servers:
dev.debug(debug: {type: "node"|"debugpy", debugpyPath}). On a remote server it gets a sync note, and its watch patterns are still written.
- stdio servers:
VS Code 1.140 calls
.vscode/mcp.jsondeprecated but still loads it, with no removal date. MCP: Add Server offers to save workspace servers to a root.mcp.jsonunder themcpServerskey, only whenchat.mcp.workspaceRootConfig.enabledis on (experimental) (VS Code MCP servers, VS Code MCP configuration, 1.140 release notes). A project without theclaudetool can opt in:outputs: copilot: root-mcp-file: .mcp.jsonSync then also writes the servers there, with the
toolslist of allowed tools and without the VS Code fields.
Why root .mcp.json is opt-in
VS Code always reads a root .mcp.json. In a trusted workspace it starts those servers, along with the ones from .vscode/mcp.json, with no prompt. VS Code does not say which file wins when both define the same server name.
The claude target writes .mcp.json too. A plain server is identical from both, so sync writes it once. A field only one tool documents, such as Claude Code's timeout or Copilot CLI's tools, makes the copies differ, and sync stops with an conflicting output files. VS Code does not say which copy wins (design notes on #1533).
Hooks
sync --global writes user hooks to ~/.copilot/hooks/agnostic-ai.json, or $COPILOT_HOME/hooks/agnostic-ai.json when set. Copilot CLI combines all user *.json hook files with repository hooks. This native user route was checked against the hooks reference on October 11, 2026.
import --global copilot reads those user files. It keeps matching handwritten handlers in their original file, so sync adds no duplicate. Unsupported entries stay native with a skip reason. If an imported neighboring handler changes, sync stops and names its file. Make the native entry match the spec, or move the original file outside the hooks directory before syncing. On a fresh machine, sync creates the imported handlers in the managed file.
Hooks go to .github/hooks/agnostic-ai.json (override with outputs.copilot.hooks-file). Copilot CLI and cloud agent load every .github/hooks/*.json and merge them (hooks reference). Copilot documents 14 events. With builtins: [memory], a SessionStart hook adds the shared memory index to the session. Its reply holds the index under additionalContext, which Copilot CLI reads, and under hookSpecificOutput.additionalContext with hookEventName: "SessionStart", which VS Code reads. A spec with commandWindows writes bash (from command) and powershell (from commandWindows) instead of one command, which Copilot would run in both shells.
The wrapper is
{"version": 1, "hooks": {...}}with an integer version. Each entry is flat,{"type": "command", "matcher": ..., "command": ..., "timeoutSec": ...}, not Claude Code's nested{matcher, hooks: [...]}group.event:is copied as written. Copilot accepts PascalCase (PreToolUse, the "VS Code compatible" format) and camelCase (preToolUse).- PascalCase matches the way Claude Code does and uses its tool names, so
matcher: Bashworks unchanged. - camelCase matches the whole value against the matcher as a regex. The value depends on the event: Copilot's lowercase tool names (
bash,edit,view, ...) onpreToolUse,postToolUse, andpermissionRequest, the subagent's name onsubagentStart,notification_typeonnotification, andmanualorautoonpreCompact(matcher filtering). - Copilot skips a hook with an invalid regex matcher.
syncreports that, a Claude tool name on a camelCase event, and a matcher on an event that documents none. userPromptTransformedandsubagentStartexist only in camelCase. A PascalCaseSubagentStart(valid in Claude Code and Codex) never fires, andvalidateflags it.
- PascalCase matches the way Claude Code does and uses its tool names, so
Command hooks also take
cwd(relative to the repository root, or absolute) andenv(with variable expansion), set at the spec's top level or underx-copilot. Both are Copilot-only and survive import.- With a relative
cwd, sync writes the script path relative to that directory. So.agnostic-ai/hooks/guard.shwithcwd: subbecomes../.agnostic-ai/hooks/guard.sh, and import restores the repository-relative path. - The path is the first word of
command, or the second after an interpreter such asbash, or it is inexecandargs. - Absolute paths,
$-prefixed paths, and commands that are not paths stay as written. So does every path whencwdis absolute or outside the repository.
- With a relative
import copilotreads back command handlers, HTTP handlers (url,headers,allowedEnvVars), andsessionStartprompt handlers.A spec with
argsis written in Copilot's form without a shell,{"type": "command", "exec": <command>, "args": [...]}, for paths or arguments with spaces. Copilot does not allowexecnext tocommand, so the program moves out ofcommand. Claude Code's form keeps it there. Only Copilot CLI runs this form. Cloud agent runs onlybashorcommandentries, sosyncadds a sync note. Leaveargsunset for a hook that must run under cloud agent.A portable
before-toolhook lands onPreToolUseand runs through.github/hooks/scripts/agnostic-ai-portable-hook.sh. Exit 2 denies the call and uses stderr aspermissionDecisionReason. Exit 1 also denies, so a broken guard keeps blocking. Claude Code reports an error and goes on instead. A hook withdecision: stdoutruns through the same wrapper. Import reads it back as the portable spec.
Every hook runs twice when you sync claude and copilot together. Copilot also reads .claude/settings.json and .claude/settings.local.json and runs every entry for an event. Both are default targets, so a formatter runs twice, an audit hook writes twice, and a blocking preToolUse returns two decisions. Unlike Cursor and Trae, Copilot has no switch to stop this. Give the hook spec a single target:.
Copilot reads Claude settings too. Copilot CLI reads companyAnnouncements, disableAllHooks, enabledPlugins, extraKnownMarketplaces, and hooks from .claude/settings.json and .claude/settings.local.json (CLI config reference). agnostic-ai writes hooks, and enabledPlugins via outputs.claude.settings.enabledPlugins, so that key also sets the Copilot CLI plugin policy for the repository. Set it on purpose, or drop claude if the two tools need different plugins. GitHub documents only the .github/copilot/settings.json keys as read by Copilot in the cloud, so for plugins both should load, use x-copilot.enabledPlugins (see Settings).
Config keys
| Key | Default | Notes |
|---|---|---|
outputs.copilot.instructions-dir | .github/instructions | |
outputs.copilot.agents-dir | .github/agents | |
outputs.copilot.skills-dir | .github/skills | |
outputs.copilot.mcp-file | .vscode/mcp.json | |
outputs.copilot.cli-mcp-file | .github/mcp.json | Copilot CLI's own file, mcpServers key |
outputs.copilot.root-mcp-file | unset, opt-in | writes the same servers to .mcp.json in the workspace root, under the mcpServers key |
outputs.copilot.chatmodes-dir | empty, opt-in | writes one Custom Chat Mode per agent |
outputs.copilot.rules-file | unset | writes all always-on rules into that one file and skips the instructions that point to source specs |
outputs.copilot.hooks-file | .github/hooks/agnostic-ai.json |
Import
agnostic-ai import copilot reads:
| Source | Becomes |
|---|---|
.github/copilot-instructions.md | .agnostic-ai/AGNOSTIC_AI.md, whole. Only a rules block that sync wrote also becomes <rules>/<name>.md specs |
.github/instructions/<name>.instructions.md | <rules>/<name>.md. Files with an agent- or skill- prefix become agents and skills |
.github/agents/<name>.agent.md and .github/chatmodes/<name>.chatmode.md | <agents>/<name>.md, keeping the frontmatter keys Copilot output does not use |
.github/skills/<name>/, .claude/skills/<name>/, .agents/skills/<name>/ | <skills>/<name>/ |
.github/hooks/*.json | one target-scoped hook spec per handler |
.vscode/mcp.json | <mcps>/<name>.yaml |
.github/copilot/settings.json model, effortLevel, enabledPlugins, extraKnownMarketplaces | <settings>/copilot.yaml: model, effort as in Settings, and the plugin keys under x-copilot, unless another settings spec for Copilot sets them |
All three skill directories are documented. On a name clash, .github/skills wins, then .claude/skills, then .agents/skills.
Protected paths
Not enforced. Copilot takes deny and ask rules only from device settings managed by your organization, not from a repository file, so sync prints a note for protected paths. State the paths in a rule if the agent should know about them.