A collection of GitHub Copilot CLI hooks.
| File | Hook event | Purpose |
|---|---|---|
tool-approver.json |
permissionRequest |
Auto-approve / deny / ask for tool calls based on configurable regex patterns |
worktree-trust.json |
sessionStart |
Automatically add git worktree directories to Copilot's trusted folders list |
The main hook. Before Copilot's permission dialog fires, this script evaluates the pending tool call against three regex lists:
allowRegex— auto-approve matching calls silentlydenyRegex— hard-block matching calls with an explanationalwaysAskRegex— force manual approval even ifallowRegexwould match
If nothing matches, the hook falls through and Copilot's normal prompt handles it.
Patterns match against a canonical string derived from the permissionRequest payload:
| Tool type | Canonical form | Example |
|---|---|---|
| File write/edit/create | write |
write |
| Shell command | shell(<command>) |
shell(dotnet build) |
| MCP tool | <serverName>(<toolName>) |
ado(wit_work_item) |
| URL fetch | url(<url>) |
url(https://example.com/) |
tool-approver/
tool-approver.py # Hook script (invoked by tool-approver.json)
tool-approver-config.jsonc # Shared allow/deny/ask patterns — edit this
tool-approver-config.local.jsonc # Personal machine overrides — not committed
tool-approver-approved.jsonl # Log: auto-approved calls (gitignored)
tool-approver-denied.jsonl # Log: denied calls (gitignored)
tool-approver-ask.jsonl # Log: always-ask calls (gitignored)
tool-approver-suggestions.jsonl # Log: fall-throughs to review (gitignored)
tool-approver.log # Diagnostic log (gitignored)
Copilot CLI loads hooks from the hooks/ folder inside your Copilot config directory. The location depends on your OS:
| OS | Hooks directory |
|---|---|
| Windows | %USERPROFILE%\.copilot\hooks\ |
| macOS / Linux | ~/.copilot/hooks/ |
The directory won't exist yet if you've never installed a hook — create it manually.
# Option A — clone directly into your hooks directory (recommended)
git clone https://github.com/wrroper/copilot-hooks ~/.copilot/hooks
# Option B — copy files manually
# Download/clone the repo somewhere, then copy the contents into ~/.copilot/hooks/On Windows (PowerShell):
git clone https://github.com/wrroper/copilot-hooks "$env:USERPROFILE\.copilot\hooks"After cloning, your hooks directory should look like this:
~/.copilot/hooks/
tool-approver.json
worktree-trust.json
tool-approver/
tool-approver.py
tool-approver-config.jsonc
tool-approver-config.local.jsonc
- Python 3.10+ must be on your
PATH(verify withpython --version) - No third-party Python packages are required — only the standard library
- Edit
tool-approver/tool-approver-config.jsoncto add, remove, or adjust allow/deny/ask patterns for the tools you use - Add machine-specific overrides to
tool-approver/tool-approver-config.local.jsonc(this file is gitignored so it won't be committed if you fork the repo) - If using
worktree-trust.json, update the path regex to match your worktree root
Start a new Copilot CLI session and trigger any tool call. If the hook is working you'll either see it auto-approved silently, denied with a message, or Copilot's normal prompt will appear (fall-through). Check tool-approver/tool-approver.log for diagnostic output.
Tip: After your first session, review
tool-approver-suggestions.jsonl. Every unmatched tool call lands there with its full canonical string — use it to tune your patterns.
The hook is fail-open: any unhandled exception outputs {} and exits 0, so Copilot's normal prompt takes over. A non-zero exit would deny the tool call entirely.
Every tool call that doesn't match any pattern is logged to tool-approver-suggestions.jsonl as a fall-through. Periodically reviewing this file and promoting entries into tool-approver-config.jsonc tightens your coverage over time.
Open tool-approver/tool-approver-suggestions.jsonl. Each line is a JSON object:
{
"canonical": "shell(npm run build)",
"decision": "fall_through",
"reason": "No pattern matched part: \"shell(npm run build)\"",
"suggested_allowRegex": "^shell\\(npm run build\\)$",
"raw_input": { ... }
}The key fields are:
| Field | What to use it for |
|---|---|
canonical |
The exact string your pattern needs to match |
suggested_allowRegex |
A ready-made strict regex — good starting point, loosen as needed |
raw_input |
Full payload from Copilot — useful if the canonical looks wrong |
For each entry, ask:
- Allow — Is this a routine, safe operation you'd approve every time without thinking? (e.g.
dotnet build, read-only MCP tools) - Deny — Should this never be allowed regardless of context? (e.g. inline SQL execution, pipe-to-interpreter)
- Always-ask — Is it legitimate but risky enough to warrant a human glance every time? (e.g.
git push,Remove-Item -Force) - Leave it — One-off or context-dependent; not worth a permanent pattern.
Edit tool-approver-config.jsonc for patterns that apply across all machines, or tool-approver-config.local.jsonc for patterns that only make sense on this machine.
Use the suggested_allowRegex as a starting point, but consider loosening it:
Add the entry to the appropriate list (allowRegex, denyRegex, or alwaysAskRegex) with a reason that explains why it's safe or unsafe. The reason is shown in the log and in Copilot's denial message, so write it as if explaining to a future reviewer.
Run the hook manually against a sample payload to confirm your regex matches as expected:
# Pipe a minimal permissionRequest payload to the script
'{"toolName":"powershell","toolInput":{"command":"dotnet build"}}' | python tool-approver/tool-approver.py
# Expected output: {"behavior":"allow"}Once you've processed a batch of entries, clear the file so the next review starts fresh:
Clear-Content tool-approver/tool-approver-suggestions.jsonlTip: Do a curation pass after any new project or tooling setup — the first few sessions in a new context generate the most unmatched patterns.
On sessionStart, if the current working directory matches a configured path pattern (e.g. your git worktrees root), the hook automatically adds it to Copilot's trustedFolders list in ~/.copilot/config.json.
Edit the regex in worktree-trust.json to match your own worktree root before using it.
Pattern suggestions and improvements welcome — open a PR or issue.
- GitHub Copilot hooks reference — full event reference, payload schemas, config format, and precedence rules
- Using hooks with GitHub Copilot CLI — official how-to guide with examples
- Copilot SDK — hooks deep-dive — lower-level detail on hook execution and the permissionRequest behavior