Agent Hooks
VS Code Agent Hooks automate code quality enforcement by running shell commands at key lifecycle points during agent sessions. They complement the instruction-based approach with deterministic, code-driven automation.
Agent hooks are a VS Code Preview feature.
Agent-scoped hooks (defined in .agent.md frontmatter) require
chat.useCustomAgentHooks: true in .vscode/settings.json.
How Hooks Work
Section titled “How Hooks Work”sequenceDiagram
participant A as 🤖 Agent
participant H as 🪝 Hook
participant T as 🔧 Tool
A->>H: PreToolUse (before tool runs)
alt Blocked
H-->>A: deny + reason
else Allowed
H-->>A: continue
A->>T: Execute tool
T-->>A: Result
A->>H: PostToolUse (after tool runs)
H-->>A: continue + advisory
end
Hook Inventory
Section titled “Hook Inventory”| Hook Directory | Event | Purpose | Timeout |
|---|---|---|---|
block-dangerous-commands/ | PreToolUse | Block dangerous terminal commands and hook self-modification | 10s |
post-edit-format/ | PostToolUse | Auto-format .md, .bicep, .tf, .js files after edits | 30s |
session-start-audit/ | SessionStart | Log session, inject project context (step, subscription, branch) | 5s |
subagent-validation/ | SubagentStop | Validate subagent output quality (advisory) | 15s |
session-report/ | Stop | Generate lightweight session summary | 180s |
Configuration
Section titled “Configuration”Hooks are registered in .vscode/settings.json:
{ "chat.hookFilesLocations": { ".github/hooks/block-dangerous-commands": true, ".github/hooks/post-edit-format": true, ".github/hooks/session-start-audit": true, ".github/hooks/subagent-validation": true, ".github/hooks/session-report": true }, "chat.useCustomAgentHooks": true}Agent-Scoped Hooks
Section titled “Agent-Scoped Hooks”Agents can define hooks in their YAML frontmatter (requires chat.useCustomAgentHooks):
hooks: PostToolUse: - type: command command: ".github/hooks/post-edit-format/post-edit-format.sh" timeout: 30If a hook is already registered globally in chat.hookFilesLocations, do not
re-define it in agent frontmatter — this causes the hook to run twice.
Use agent-scoped hooks only for agent-specific logic not covered by global hooks.
Safety Considerations
Section titled “Safety Considerations”Infinite Loop Prevention
Section titled “Infinite Loop Prevention”The Stop hook (session-report.sh) checks stop_hook_active from stdin JSON. If true,
it returns immediately without processing — preventing infinite re-invocation.
Self-Modification Protection
Section titled “Self-Modification Protection”The PreToolUse hook blocks file-edit tools (replace_string_in_file, create_file, etc.)
from modifying files under .github/hooks/. Path resolution uses realpath to handle
symlinks and traversal attacks (../).
Timeout Enforcement
Section titled “Timeout Enforcement”Each hook specifies a timeout (5-180s). If a hook exceeds its timeout, VS Code terminates it and continues the agent session.
SessionId Sanitization
Section titled “SessionId Sanitization”The Stop hook sanitizes sessionId input to prevent path traversal — only alphanumeric
characters, hyphens, and underscores are preserved.
Hook Directory Structure
Section titled “Hook Directory Structure”Each hook follows this pattern:
.github/hooks/{name}/├── hooks.json # Event binding + timeout└── {name}.sh # Shell script (must be executable)hooks.json Schema
Section titled “hooks.json Schema”{ "hooks": { "<EventName>": [ { "type": "command", "command": ".github/hooks/{name}/{name}.sh", "timeout": 30 } ] }}Valid event names: PreToolUse, PostToolUse, SessionStart, SubagentStart,
SubagentStop, Stop.
Script Conventions
Section titled “Script Conventions”All hook scripts must:
- Start with
#!/usr/bin/env bash - Include
set -euo pipefail - Read JSON from stdin
- Write JSON to stdout (
{"continue": true}or{"hookSpecificOutput": {...}}) - Be executable (
chmod +x)
Validation
Section titled “Validation”# Validate hook configurationsnpm run validate:hooks
# Run hook integration testsnpm run test:hooksAdding a New Hook
Section titled “Adding a New Hook”1. Create the hook directory and script
Section titled “1. Create the hook directory and script”mkdir -p .github/hooks/my-hook-nameCreate .github/hooks/my-hook-name/my-hook-name.sh:
#!/usr/bin/env bashset -euo pipefail
INPUT=$(cat)
# Parse input JSONFIELD=$(echo "$INPUT" | python3 -c \ "import sys,json; print(json.load(sys.stdin).get('field',''))" \ 2>/dev/null || echo "")
# Your logic here...
# Output JSON safely (prevents injection)python3 -c "import json, sysprint(json.dumps({'continue': True, 'systemMessage': sys.argv[1]}))" "Your message" 2>/dev/null || echo '{"continue": true}'Then: chmod +x .github/hooks/my-hook-name/my-hook-name.sh
2. Create hooks.json
Section titled “2. Create hooks.json”{ "hooks": { "PostToolUse": [ { "type": "command", "command": ".github/hooks/my-hook-name/my-hook-name.sh", "timeout": 30 } ] }}3. Register in VS Code settings
Section titled “3. Register in VS Code settings”Add to chat.hookFilesLocations in .vscode/settings.json:
".github/hooks/my-hook-name": true4. Add tests and validate
Section titled “4. Add tests and validate”Add test cases to scripts/test-hooks.sh, then run:
npm run validate:hooksnpm run test:hooksBest practices
Section titled “Best practices”- JSON safety: Always use
python3 json.dumps()for output — never string interpolation - Fast execution: Keep hooks under their timeout; check tool availability with
command -v - No network calls: Hooks should be fast and local
- Path safety: Use
realpathand verify paths are within the repository - Error handling: Use
set -euo pipefail; handle missing tools gracefully
Troubleshooting
Section titled “Troubleshooting”Hook Not Firing
Section titled “Hook Not Firing”- Verify the hook directory is listed in
chat.hookFilesLocationsin.vscode/settings.json - Check the script is executable:
ls -la .github/hooks/{name}/{name}.sh - View hook output: Output panel → GitHub Copilot Chat Hooks channel
Hook Timeout
Section titled “Hook Timeout”If a hook exceeds its timeout, VS Code kills the process and continues. Check for:
- Network calls in hooks (avoid — hooks should be fast and local)
- Large file processing (the >1MB guard in post-edit-format prevents this)
- Missing tool binaries (hooks should check
command -vbefore running tools)
Manual Testing
Section titled “Manual Testing”Test a hook locally by piping mock JSON:
echo '{"tool_name":"run_in_terminal","tool_input":{"command":"ls"}}' | \ bash .github/hooks/block-dangerous-commands/block-dangerous-commands.shRelationship to Git Hooks
Section titled “Relationship to Git Hooks”Agent hooks (.github/hooks/) and git hooks (lefthook.yml) serve different purposes:
| Agent Hooks | Git Hooks (lefthook) | |
|---|---|---|
| When | During agent sessions | On git commit/push |
| Scope | Individual tool invocations | Staged/changed files |
| Config | .github/hooks/*/hooks.json | lefthook.yml |
| Purpose | Real-time quality enforcement | Pre-commit/pre-push validation |
Both systems complement each other — agent hooks catch issues during authoring, git hooks catch issues before commit.