diff --git a/docs/copilot/agents/agent-tools.md b/docs/copilot/agents/agent-tools.md index 579dfe3eef..b82f6a7426 100644 --- a/docs/copilot/agents/agent-tools.md +++ b/docs/copilot/agents/agent-tools.md @@ -386,4 +386,5 @@ Yes. You can create tools in two ways: ## Related resources * [Chat tools reference](/docs/copilot/reference/copilot-vscode-features.md#chat-tools) +* [Agent hooks](/docs/copilot/customization/hooks.md) - Execute custom commands at tool lifecycle events * [Security considerations for using AI in VS Code](/docs/copilot/security.md) diff --git a/docs/copilot/agents/overview.md b/docs/copilot/agents/overview.md index da4d4d961f..726f3d602e 100644 --- a/docs/copilot/agents/overview.md +++ b/docs/copilot/agents/overview.md @@ -217,4 +217,6 @@ To permanently delete an agent session, right-click the session in the sessions * [Tools](/docs/copilot/agents/agent-tools.md): Extend agents with built-in, MCP, and extension tools. +* [Hooks](/docs/copilot/customization/hooks.md): Execute custom commands at lifecycle events for automation and policy enforcement + * [Custom agents](/docs/copilot/customization/custom-agents.md): Create your own AI agents and extensions. diff --git a/docs/copilot/customization/hooks.md b/docs/copilot/customization/hooks.md new file mode 100644 index 0000000000..62bc36107b --- /dev/null +++ b/docs/copilot/customization/hooks.md @@ -0,0 +1,489 @@ +--- +ContentId: 9c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f +DateApproved: 02/09/2026 +MetaDescription: Learn how to use hooks in VS Code to execute custom shell commands at key lifecycle points during agent sessions for automation, validation, and policy enforcement. +MetaSocialImage: ../images/shared/github-copilot-social.png +Keywords: +- copilot +- ai +- agents +- hooks +- automation +- lifecycle +- preToolUse +- postToolUse +--- + +# Agent hooks in Visual Studio Code (Preview) + +Hooks enable you to execute custom shell commands at key lifecycle points during agent sessions. Use hooks to automate workflows, enforce security policies, validate operations, and integrate with external tools. Hooks run deterministically and can control agent behavior, including blocking tool execution or injecting context into the conversation. + +> [!NOTE] +> Agent hooks are currently in Preview for VS Code Insiders 1.110. The configuration format and behavior might change in future releases. + +Hooks are designed to work across agent types, including local agents, background agents, and cloud agents. Each hook receives structured JSON input and can return JSON output to influence agent behavior. + +## Why use hooks? + +Hooks provide deterministic, code-driven automation. Unlike instructions or custom prompts that guide agent behavior, hooks execute your code at specific lifecycle points with guaranteed outcomes: + +* **Enforce security policies**: Block dangerous commands like `rm -rf` or `DROP TABLE` before they execute, regardless of how the agent was prompted. + +* **Automate code quality**: Run formatters, linters, or tests automatically after file modifications. + +* **Create audit trails**: Log every tool invocation, command execution, or file change for compliance and debugging. + +* **Inject context**: Add project-specific information, API keys, or environment details to help the agent make better decisions. + +* **Control approvals**: Automatically approve safe operations while requiring confirmation for sensitive ones. + +## Hook lifecycle events + +VS Code supports eight hook events that fire at specific points during an agent session: + +| Hook Event | When It Fires | Common Use Cases | +|------------|---------------|------------------| +| `SessionStart` | New agent session begins | Initialize resources, log session start, validate project state | +| `UserPromptSubmit` | User submits a prompt | Audit user requests, inject system context | +| `PreToolUse` | Before agent invokes any tool | Block dangerous operations, require approval, modify tool input | +| `PostToolUse` | After tool completes successfully | Run formatters, log results, trigger follow-up actions | +| `PreCompact` | Before conversation context is compacted | Export important context, save state before truncation | +| `SubagentStart` | Subagent is spawned | Track nested agent usage, initialize subagent resources | +| `SubagentStop` | Subagent completes | Aggregate results, cleanup subagent resources | +| `Stop` | Agent session ends | Generate reports, cleanup resources, send notifications | + +## Configure hooks + +Hooks are configured in JSON files stored in your workspace or user directory. + +### Hook file locations + +VS Code searches for hook configuration files in these locations: + +* **Workspace**: `.github/hooks/*.json` - Project-specific hooks shared with your team +* **Workspace**: `.claude/settings.local.json` - Local workspace hooks (not committed) +* **Workspace**: `.claude/settings.json` - Workspace-level hooks +* **User**: `~/.claude/settings.json` - Personal hooks applied across all workspaces + +Workspace hooks take precedence over user hooks for the same event type. + +### Hook configuration format + +Create a JSON file with a `hooks` object containing arrays of hook commands for each event type. VS Code uses the same hook format as Claude Code and Copilot CLI for compatibility: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/validate-tool.sh", + "timeoutSec": 15 + } + ], + "PostToolUse": [ + { + "type": "command", + "command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\"" + } + ] + } +} +``` + +### Hook command properties + +Each hook entry must have `type: "command"` and at least one command property: + +| Property | Type | Description | +|----------|------|-------------| +| `type` | string | Must be `"command"` | +| `command` | string | Default command to run (cross-platform) | +| `windows` | string | Windows-specific command override | +| `linux` | string | Linux-specific command override | +| `osx` | string | macOS-specific command override | +| `cwd` | string | Working directory (relative to repository root) | +| `env` | object | Additional environment variables | +| `timeoutSec` | number | Timeout in seconds (default: 30) | + +> [!NOTE] +> OS-specific commands are selected based on the extension host platform. In remote development scenarios (SSH, Containers, WSL), this might differ from your local operating system. + +### OS-specific commands + +Specify different commands for each operating system: + +```json +{ + "hooks": { + "PostToolUse": [ + { + "type": "command", + "command": "./scripts/format.sh", + "windows": "powershell -File scripts\\format.ps1", + "linux": "./scripts/format-linux.sh", + "osx": "./scripts/format-mac.sh" + } + ] + } +} +``` + +The execution service selects the appropriate command based on your OS. If no OS-specific command is defined, it falls back to the `command` property. + +## Hook input and output + +Hooks communicate with VS Code through stdin (input) and stdout (output) using JSON. + +### Input format + +Every hook receives a JSON object via stdin with common fields: + +```json +{ + "timestamp": "2026-02-09T10:30:00.000Z", + "cwd": "/path/to/workspace", + "sessionId": "session-identifier", + "hookEventName": "PreToolUse", + "transcript_path": "/path/to/transcript.json" +} +``` + +For `PreToolUse` and `PostToolUse` hooks, additional fields are included: + +```json +{ + "tool_name": "editFiles", + "tool_input": { "files": ["src/main.ts"] }, + "tool_use_id": "tool-123", + "tool_response": "File edited successfully" +} +``` + +The `tool_response` field is only present for `PostToolUse` hooks. + +For `UserPromptSubmit` hooks, a `prompt` field is included with the text the user submitted. + +For `Stop` and `SubagentStop` hooks, a `stop_hook_active` boolean is included. It is `true` when a stop hook is already keeping the session active. + +### Output format + +Hooks can return JSON via stdout to influence agent behavior: + +```json +{ + "continue": true, + "stopReason": "Security policy violation", + "systemMessage": "Operation blocked by security hook" +} +``` + +| Field | Type | Description | +|-------|------|-------------| +| `continue` | boolean | Set to `false` to stop processing (default: `true`) | +| `stopReason` | string | Reason for stopping (shown to the model) | +| `systemMessage` | string | Message displayed to the user | + +### Exit codes + +The hook's exit code determines how VS Code handles the result: + +| Exit Code | Behavior | +|-----------|----------| +| `0` | Success: parse stdout as JSON | +| `2` | Blocking error: stop processing and show error to model | +| Other | Non-blocking warning: show warning to user, continue processing | + +## PreToolUse hook output + +The `PreToolUse` hook can control tool execution through a `hookSpecificOutput` object: + +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": "Destructive command blocked by policy", + "updatedInput": { "files": ["src/safe.ts"] }, + "additionalContext": "User has read-only access to production files" + } +} +``` + +| Field | Values | Description | +|-------|--------|-------------| +| `permissionDecision` | `"allow"`, `"deny"`, `"ask"` | Controls tool approval | +| `permissionDecisionReason` | string | Reason shown to user | +| `updatedInput` | object | Modified tool input (optional) | +| `additionalContext` | string | Extra context for the model | + +**Permission decision priority**: When multiple hooks run for the same tool invocation, the most restrictive decision wins: +1. `deny` (most restrictive): blocks tool execution +2. `ask`: requires user confirmation +3. `allow` (least restrictive): auto-approves execution + +## Configure hooks with the /hooks command + +Use the `/hooks` slash command in chat to configure hooks through an interactive UI: + +1. Type `/hooks` in the chat input and press `kbstyle(Enter)`. + +1. Select a hook event type from the list. + +1. Choose an existing hook to edit or select **Add new hook** to create one. + +1. Select or create a hook configuration file. + +The command opens the hook file in the editor with your cursor positioned at the command field, ready for editing. + +## Usage scenarios + +The following examples demonstrate common hook patterns. + +
+Block dangerous terminal commands + +Create a `PreToolUse` hook that prevents destructive commands: + +**.github/hooks/security.json**: +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/block-dangerous.sh", + "timeoutSec": 5 + } + ] + } +} +``` + +**scripts/block-dangerous.sh**: +```bash +#!/bin/bash +INPUT=$(cat) +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name') +TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input') + +if [ "$TOOL_NAME" = "runTerminalCommand" ]; then + COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty') + + if echo "$COMMAND" | grep -qE '(rm\s+-rf|DROP\s+TABLE|DELETE\s+FROM)'; then + echo '{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"Destructive command blocked by security policy"}}' + exit 0 + fi +fi + +echo '{"continue":true}' +``` + +
+ +
+Auto-format code after edits + +Run Prettier automatically after any file modification: + +**.github/hooks/formatting.json**: +```json +{ + "hooks": { + "PostToolUse": [ + { + "type": "command", + "command": "./scripts/format-changed-files.sh", + "windows": "powershell -File scripts\\format-changed-files.ps1", + "timeoutSec": 30 + } + ] + } +} +``` + +**scripts/format-changed-files.sh**: +```bash +#!/bin/bash +INPUT=$(cat) +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name') + +if [ "$TOOL_NAME" = "editFiles" ] || [ "$TOOL_NAME" = "createFile" ]; then + FILES=$(echo "$INPUT" | jq -r '.tool_input.files[]? // .tool_input.path // empty') + + for FILE in $FILES; do + if [ -f "$FILE" ]; then + npx prettier --write "$FILE" 2>/dev/null + fi + done +fi + +echo '{"continue":true}' +``` + +
+ +
+Log tool usage for auditing + +Create an audit trail of all tool invocations: + +**.github/hooks/audit.json**: +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/log-tool-use.sh", + "env": { + "AUDIT_LOG": ".github/hooks/audit.log" + } + } + ] + } +} +``` + +**scripts/log-tool-use.sh**: +```bash +#!/bin/bash +INPUT=$(cat) +TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp') +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name') +SESSION_ID=$(echo "$INPUT" | jq -r '.sessionId') + +echo "[$TIMESTAMP] Session: $SESSION_ID, Tool: $TOOL_NAME" >> "${AUDIT_LOG:-audit.log}" +echo '{"continue":true}' +``` + +
+ +
+Require approval for specific tools + +Force manual confirmation for tools that modify infrastructure: + +**.github/hooks/approval.json**: +```json +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "./scripts/require-approval.sh" + } + ] + } +} +``` + +**scripts/require-approval.sh**: +```bash +#!/bin/bash +INPUT=$(cat) +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name') + +# Tools that should always require approval +SENSITIVE_TOOLS="runTerminalCommand|deleteFile|pushToGitHub" + +if echo "$TOOL_NAME" | grep -qE "^($SENSITIVE_TOOLS)$"; then + echo '{"hookSpecificOutput":{"permissionDecision":"ask","permissionDecisionReason":"This operation requires manual approval"}}' +else + echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}' +fi +``` + +
+ +
+Inject project context at session start + +Provide project-specific information when a session begins: + +**.github/hooks/context.json**: +```json +{ + "hooks": { + "SessionStart": [ + { + "type": "command", + "command": "./scripts/inject-context.sh" + } + ] + } +} +``` + +**scripts/inject-context.sh**: +```bash +#!/bin/bash +PROJECT_INFO=$(cat package.json 2>/dev/null | jq -r '.name + " v" + .version' || echo "Unknown project") +BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") + +cat </dev/null || echo 'not installed')" +} +EOF +``` + +
+ +## Troubleshooting + +### View hook diagnostics + +To see which hooks are loaded and check for configuration errors: + +1. Right-click in the Chat view and select **Diagnostics**. + +1. Look for the hooks section to see loaded hooks and any validation errors. + +### View hook output + +To review hook output and errors: + +1. Open the **Output** panel. + +1. Select **Hooks** from the channel list. + +### Common issues + +**Hook not executing**: Verify the hook file is in `.github/hooks/` and has a `.json` extension. Check that the `type` property is set to `"command"`. + +**Permission denied errors**: Ensure your hook scripts have execute permissions (`chmod +x script.sh`). + +**Timeout errors**: Increase the `timeoutSec` value or optimize your hook script. The default is 30 seconds. + +**JSON parse errors**: Verify your hook script outputs valid JSON to stdout. Use `jq` or a JSON library to construct output. + +## Frequently asked questions + +### How does VS Code handle Claude Code hook configurations? + +VS Code parses Claude Code's hook configuration format, including matcher syntax. Currently, VS Code ignores matcher values, so hooks apply to all tools. Claude Code uses an empty string matcher (`""`) to represent all tools. + +### How does VS Code handle Copilot CLI hook configurations? + +VS Code parses Copilot CLI hook configurations and converts the lowerCamelCase hook event names (like `preToolUse`) to the PascalCase format used by VS Code (`PreToolUse`). Both `bash` and `powershell` command formats are supported. + +## Security considerations + +> [!CAUTION] +> Hooks execute shell commands with the same permissions as VS Code. Review hook configurations carefully, especially when using hooks from untrusted sources. + +* **Review hook scripts**: Inspect all hook scripts before enabling them, especially in shared repositories. + +* **Limit hook permissions**: Use the principle of least privilege. Hooks should only have access to what they need. + +* **Validate input**: Hook scripts receive input from the agent. Validate and sanitize all input to prevent injection attacks. + +* **Secure credentials**: Never hardcode secrets in hook scripts. Use environment variables or secure credential storage. + +## Related resources + +* [Use tools with agents](/docs/copilot/agents/agent-tools.md) - Learn about tool approval and execution +* [Custom agents](/docs/copilot/customization/custom-agents.md) - Create specialized agent configurations +* [Subagents](/docs/copilot/agents/subagents.md) - Delegate tasks to context-isolated subagents +* [Security considerations](/docs/copilot/security.md) - Best practices for AI security in VS Code diff --git a/docs/toc.json b/docs/toc.json index 5f202a68a5..1f7c1932fe 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -128,7 +128,8 @@ ["Custom Agents", "/docs/copilot/customization/custom-agents"], ["Skills", "/docs/copilot/customization/agent-skills"], ["Language Models", "/docs/copilot/customization/language-models"], - ["MCP", "/docs/copilot/customization/mcp-servers"] + ["MCP", "/docs/copilot/customization/mcp-servers"], + ["Hooks", "/docs/copilot/customization/hooks"] ] } ],