Understanding the true power of GitHub Copilot custom agents.
GitHub Copilot custom agents are super helpful, and easy to create, but they are super powerful too. If you're not careful, they can do things you may not want, and cost you more money than expected.
Leave the tools line out of a custom agent and Copilot gives it everything. That costs money, eats your context window, widens your attack surface and, worst of all, gives the model ideas. Here's why it matters, how child agents and personal agents can get round it, and how to keep it under control.
I've spent a lot of time recently looking at GitHub Copilot custom agents, both my own and ones that customers have written. They're brilliant. Drop a single .agent.md file into .github/agents and you have a reusable planner, reviewer or test writer that the whole team can pick from the agent dropdown.
But there's one line that's missing far more often than it should be:
tools: ['read', 'search']
The tools property is optional, so people leave it out. The agent still works, so nobody notices. Here's the kind of thing I see:
---
name: planner
description: Creates implementation plans for new features
---
You are a planning specialist. Analyse the request and produce a step-by-step
implementation plan in markdown. Do NOT edit any files. Do NOT run any commands.
Looks sensible. The instructions are clear. So what's the problem?
What happens when you leave it out
The GitHub documentation is clear: if tools is unset, the agent gets all available tools. That includes every MCP server tool configured in the agent profile or the repository settings. The Copilot CLI docs say the same: by default, custom agents get every tool. And in the CLI, even a tools line isn't an exact limit: the usual default tools are added back unless you also set strict-tools-list: true.
In VS Code's Local harness it's slightly different, but no better. The agent gets whatever happens to be ticked in the tools picker. So the same agent file can behave one way on my machine and a completely different way on yours.
Even GitHub's own example "test specialist" agent leaves the tools line out and relies on its instructions to keep it away from production code.
So our polite little planner can edit files, run shell commands, fetch web pages, call every MCP server you've connected and hand work off to other agents. It's been told not to. But it can.
Here's why that matters.
1. Cost
On 1 June 2026, GitHub Copilot moved to usage-based billing. Instead of counting premium requests, usage now consumes GitHub AI Credits, calculated from input, output and cached tokens.
Every tool an agent has comes with a definition (a name, a description and a schema), and those definitions are sent to the model as part of its context. More tools means more input tokens on every turn, and agents take a lot of turns.
There's a second, sneakier cost. An agent with lots of tools has lots of options, so it explores more. GitHub found this themselves: when they trimmed the default set of built-in tools in VS Code from 40 to 13, success rates went up by 2 to 5 percentage points and responses got around 400 milliseconds faster. Fewer tools, better results.
To be fair, the Copilot harness is getting cleverer. Prompt caching reuses the unchanged start of the prompt, tool definitions included, instead of reprocessing it every turn. Cached input is billed at 5 to 10% of the normal input price, depending on the model. It isn't free, though. Anthropic models also charge to write to the cache, at 125% of the input price. Those figures are correct at the time of writing (November 2026) but are liable to change, so check GitHub's current rates before relying on them. And the saving only lasts while the start of the prompt stays the same. GitHub's own advice is to avoid changing tool configuration mid-session and to use only the tools you need, because changes break cache reuse.
That's another argument for a tools line. A short, fixed list is cheap to cache and doesn't shift underneath you. Leave it out in VS Code's Local harness and the agent follows the tools picker, so toggling tools there mid-session can break the cache. Newer sessions can also defer tool definitions until they're needed. But that's the harness tidying up after you. The tools line is the lever you control.
2. Context size
The context window is the model's working memory, and it's finite. GitHub's own docs describe tool definitions as always present, taking up a fixed portion of it.
If you want to see this for yourself, run /context in the Copilot CLI. It shows how much of the window is going on system tools and MCP tools before you've typed a word. In VS Code, the token usage indicator in the chat panel gives you the same breakdown, with tool definitions listed as fixed overhead on every request.
Every token spent describing a browser automation tool your planner will never use is a token not spent on your code. In a long session that means compaction kicks in sooner, and the detail you discussed twenty minutes ago gets summarised away.
3. Security
This is the one that worries me most.
Least privilege isn't a new idea. We wouldn't give a reporting service account Owner on an Azure subscription, so why give a planning agent a shell?
An agent with all tools has execute (bash or PowerShell), edit, web access and every MCP tool you've installed. That might include write access to Azure, a database or your issue tracker.
Now add prompt injection, which OWASP ranks as the number one risk for LLM applications. The agent reads a README, an issue, a dependency's docs or a web page with hidden instructions in it. A read-only agent that falls for that can still give you a bad answer, or leak something sensitive in its reply. An agent with a terminal and network access can actually do something about it. Restricting the model to the minimum access it needs is one of OWASP's own recommended mitigations.
There are good protections. The cloud agent runs in its own environment with a read-only GitHub MCP server by default, and VS Code has approvals and agent sandboxing. But VS Code's own security guidance points out that outbound network access stays enabled by default, even with sandboxing turned on. And let's be honest, plenty of us click "Always allow" to make the prompts go away.
Defence in depth means not handing the tool over in the first place.
4. "I told it not to, and it did it anyway"
This is the one people find hard to believe until they've seen it.
Models are trained to be helpful and to finish the job. If the obvious next step is to run the tests, install a package or "just fix" the bug it spotted, and the tool to do it is sitting right there, it will quite often go ahead, whatever the "Do NOT" in the prompt says.
A planning agent decides it would be helpful to run the build to check its plan. A reviewer quietly fixes the issues it was only meant to report. Nobody asked for either.
The VS Code docs say it plainly: instructions guide the model, they don't guarantee compliance, and "a 'do not edit' instruction alone is not a permission boundary."
Writing the instruction in CAPITALS doesn't fix it. Removing the tool does. A model can't run a command it hasn't been given.
Watch out for one more trap in the Copilot CLI. When a custom agent runs as a subagent, it doesn't follow your repository's custom instructions, such as .github/copilot-instructions.md or AGENTS.md, unless you add include-custom-instructions: true. So any "do not" rules you put there won't reach it either.
Don't forget the choice of agents
One more that catches people out: agent is a tool too. It's also the most powerful tool on the list, because a custom agent called as a child doesn't inherit its caller's restrictions. It overrides them.
You might expect a subagent to get the overlap between its own tools and its parent's. It doesn't. In VS Code, a plain subagent inherits the main agent's tools, but a custom agent called as a subagent brings its own tools and model, and those replace the caller's. So a read-only coordinator with tools: ['read', 'search', 'agent'] can hand work to an implementer with edit and execute, and that work runs with the implementer's permissions, not the coordinator's.
It's worse if the child has no tools line at all. It doesn't fall back to its parent's tools. It falls back to the default, which on GitHub is every available tool and in VS Code is whatever's ticked in the tools picker.
So the honest way to read a tools line that includes agent is: this agent can do anything that any agent it can reach can do. Its real permissions are everything its workers can do, added together. The permissions haven't gone away. They've just been passed through another agent.
If you need an orchestrator, here's how I'd keep it in check:
- Leave
agentout unless the agent genuinely needs to delegate. - Restrict which workers it can call. In VS Code's Local harness, combine the
agenttool with anagentslist. GitHub's custom agent configuration doesn't have an equivalent, so elsewhere the workers' own settings are your main control. Be aware that leavingagentsout allows every available agent, whileagents: []stops it calling any. - Give every worker its own narrow
toolsline, and review the coordinator as if it had all its workers' tools, because in practice it does. - Set
disable-model-invocation: trueon powerful agents so they aren't picked up automatically. Be aware that in Local, explicitly listing an agent inagentsoverrides that setting. - Leave nested delegation off. In Local, a subagent can only call further subagents if you enable
chat.subagents.allowInvocationsFromSubagents. Even then, nesting stops at five levels, which is a backstop, not a boundary.
---
name: coordinator
description: Breaks work down and delegates to named specialists
tools: ['read', 'search', 'agent']
agents: ['Researcher', 'Reviewer']
---
---
name: Researcher
description: Finds relevant code and returns findings with file references
tools: ['read', 'search']
user-invocable: false
---
Make the tools line someone's job: CODEOWNERS
Getting the tools line right once isn't enough. Agent files are just Markdown in the repo, so anyone with write access can quietly change ['read', 'search'] to ['*'] in a pull request titled "small tweak". So can an agent with edit, if it decides its own definition is holding it back.
This is exactly what CODEOWNERS is for. Put a CODEOWNERS file in .github/ and give your agent definitions, and the MCP configuration that feeds them, to a team that understands what those tools can do:
# Who decides what Copilot agents are allowed to do
/.github/agents/ @your-org/platform-engineering
/.mcp.json @your-org/platform-engineering
/.vscode/mcp.json @your-org/platform-engineering
# Protect this file too, or the rules above can be edited away
/.github/CODEOWNERS @your-org/platform-engineering
Two things catch people out. First, CODEOWNERS on its own only requests a review. To make it stick, turn on "Require review from Code Owners" in your branch protection rule or a ruleset. Second, the owning team needs write access to the repository, otherwise GitHub won't assign it.
If you share agents across an organisation, do the same in the organisation's .github or .github-private repository, where organisation-level agents live. There, agents sit in /agents/ at the root, not .github/agents/, so adjust the paths.
It's not a silver bullet. CODEOWNERS protects what gets merged, not what's on someone's laptop. In the Copilot CLI, for example, a personal agent with the same ID (taken from its filename) overrides the repository one. But it does mean the shared version of every agent has been checked by someone who knows what execute can do.
What about personal agents?
CODEOWNERS only helps with agents that live in a repository, and plenty don't. In the Copilot CLI anyone can drop an agent into ~/.copilot/agents/. VS Code has user-level agents in your profile, and agent plugins from a marketplace that bring their own. Nobody reviews those.
You can't police every file on every laptop, so for personal agents the control has to move from the agent file to the environment around it. The good news is that the tools line is a filter, not a grant. It narrows the set of tools the environment makes available. It can't conjure up a tool that isn't there.
Here are the risks I'd worry about, and what to do about each:
Nobody reviews it
- What can go wrong: A personal or marketplace agent never goes through a pull request, so nobody checks its
toolsline or its instructions. - Fix: Read the whole file before you install it, not just the
toolsline. Prefer agents from a repository or marketplace your organisation trusts.
Shadowing
- What can go wrong: In the Copilot CLI, a personal agent with the same ID as a repository agent wins. The ID comes from the filename; the
namefield doesn't count. A developer's ownreviewer.agent.mdwith notoolsline quietly replaces the team's carefully scoped one. - Fix: Give shared agents distinctive filenames, and select them explicitly.
Picked automatically
- What can go wrong: Copilot can delegate to any of your custom agents if its description looks like a good fit, even if you never selected it.
- Fix: Set
disable-model-invocation: trueon any personal agent with broad tools.
Hooks
- What can go wrong: Hooks run commands at points in an agent's lifecycle without the model asking. A read-only
toolsline doesn't stop them, and they can come from your user folder, a plugin or the agent itself. - Fix: Check any agent or plugin for hooks before installing it. Organisations can restrict which hooks can run. If you rely on a hook to block things, test it: an error blocks the tool, but a timeout lets it run under normal permissions, even for policy hooks.
Inherited approvals
- What can go wrong: Saved approvals are scoped to a session, a workspace or "always", not to a particular agent. A new agent can benefit from trust you gave an old one.
- Fix: Prefer session-scoped approvals, and reset saved permissions now and again.
Malicious instructions
- What can go wrong: An agent's body is trusted instructions. A downloaded one can tell the model to do things you'd never ask for. It's prompt injection you installed yourself.
- Fix: Read the instructions. Limit the damage with an MCP allowlist, sandboxing and deny rules.
Expensive models
- What can go wrong: The
modelline lets an agent pick any model you have access to, and under usage-based billing that comes out of your credits. - Fix: Restrict which models are available, and set budgets at enterprise, cost centre or user level.
Extra MCP servers
- What can go wrong: Agent plugins can bundle their own MCP servers, and those bring their own tools.
- Fix: Restrict MCP servers to an approved list. GitHub recommends doing this through enterprise managed settings.
Weakened local guardrails
- What can go wrong: Anyone can switch on auto-approval, choose Allow all or turn off sandboxing in their own settings.
- Fix: Lock those settings down with Copilot enterprise-managed settings or VS Code device policies, which override user and workspace settings.
Notice where most of those fixes live: in the environment, not the agent file. Managed settings cover both VS Code and the Copilot CLI, and deny rules from different sources combine in the most restrictive direction. In the CLI, a deny rule always beats an allow rule, even when --allow-all is set. Set those controls once and they apply to every agent, personal or not.
And if you're an individual developer with no admin behind you? Treat someone else's agent like someone else's script. Read it before you install it, and if it hasn't got a tools line, add one. In the CLI you can also cap a session yourself:
copilot --excluded-tools='web_fetch, web_search'
What I do now
- Always write a
toolsline. Even if it'stools: ['*'], make it a decision rather than an accident. In the Copilot CLI, addstrict-tools-list: trueso the list is exact. - Start with
readandsearch. Addeditwhen the agent genuinely needs to change files, andexecuteonly when it genuinely needs a terminal. - Name MCP tools individually (
server-name/tool-name, notserver-name/*) unless you really need the lot. - Use
tools: []for agents that only need to talk. - Treat
agentas every tool its workers have. Restrict which agents it can call withagents, give every worker its own narrowtoolsline, and usedisable-model-invocationon powerful agents. - Check what it actually did. Review the tool calls, not just the output, and use
/contextto see what's loaded. - Catch it in review. This rough check lists any agent file without a
toolsline, and is easy to drop into a pipeline. It uses*.mdbecause agent files can end in plain.mdas well as.agent.md:
grep -L '^tools:' .github/agents/*.md
If it prints anything, someone has handed an agent the keys. It's only a quick check, though: a tools: line in the body text will fool it, so use a proper YAML check if you need certainty.
- Put CODEOWNERS on your agents. Own
.github/agents/and your MCP config files, and require code owner review. Thegrepcheck catches a missing line. CODEOWNERS catches someone quietly widening it. - Don't forget personal agents. Control which tools, models and hooks are available centrally, and read the whole of any agent you download before you use it.
In my view
The tools line is the most important line in a custom agent. The instructions describe what you'd like the agent to do. The tools decide what it can do. When the two disagree, the tools win.
Setting it costs less, keeps the context clean, shrinks the blast radius and stops the model getting creative. All for one line of YAML, and a few lines of CODEOWNERS to keep it that way.
What are your thoughts? Have you had an agent go off-piste because it had a tool it didn't need? Feel free to leave comments.
Further reading
- About custom agents (GitHub Docs)
- Creating and using custom agents for GitHub Copilot CLI (GitHub Docs)
- Custom agents configuration (GitHub Docs)
- Custom agents in VS Code
- Custom agent file reference (VS Code)
- Managing context for GitHub Copilot (GitHub Docs)
- GitHub Copilot is moving to usage-based billing (GitHub Blog)
- Models and pricing for GitHub Copilot (GitHub Docs)
- How we're making GitHub Copilot smarter with fewer tools (GitHub Blog)
- GitHub Copilot CLI command reference (GitHub Docs)
- Your first agent session: approvals, autonomy and context budget (VS Code)
- Use subagents in Visual Studio Code
- Manage approvals and permissions (VS Code)
- Secure AI-assisted development in VS Code
- Understand trust and safety for AI agents (VS Code)
- Getting more from each token: How Copilot improves context handling and model routing (GitHub Blog)
- About code owners (GitHub Docs)
- MCP configuration reference (VS Code)
- Allowing and denying tool use (GitHub Docs, Copilot CLI)
- Restrict MCP server access to a custom registry (GitHub Docs)
- Manage AI settings in enterprise environments (VS Code)
- Centrally manage VS Code settings with policies (VS Code)
- Configure agent hooks in Visual Studio Code
- Managing availability of models in your enterprise (GitHub Docs)
- LLM01:2025 Prompt Injection (OWASP)
- Best practices for Azure RBAC (Microsoft Learn)