Hooks let you run automated scripts at key moments during a Copilot agent session — when a session starts or ends, when the user submits a prompt, or before and after the agent uses a tool. They’re the glue between Copilot’s AI capabilities and your team’s existing tooling: linters, formatters, governance scanners, and notification systems.

This article explains how hooks work, how to configure them, and practical patterns for common automation needs.

What Are Hooks?

Hooks are shell commands or scripts that run automatically in response to lifecycle events during a Copilot agent session. They execute outside the AI model, they’re deterministic, repeatable, and under your full control.

Key characteristics:

  • Hooks run as shell commands on the user’s machine
  • They execute synchronously—the agent waits for them to complete
  • They can block actions (e.g., prevent commits that fail linting)
  • They’re defined in JSON files stored at .github/hooks/*.json in your repository
  • They receive detailed context via JSON input, enabling context-aware automation
  • They can include bundled scripts for complex logic

When to Use Hooks vs Other Customizations

Use CaseBest Tool
Run a linter after every code changeHook
Teach Copilot your coding standardsInstruction
Automate a multi-step workflowSkill or Agent
Scan prompts for sensitive dataHook
Format code before committingHook
Generate tests for new codeSkill

Hooks are ideal for deterministic automation that must happen reliably—things you don’t want to depend on the AI remembering to do.

Anatomy of a Hook

Each hook in this repository is a folder containing:

hooks/
└── my-hook/
    ├── README.md          # Documentation with frontmatter
    ├── hooks.json         # Hook configuration
    └── scripts/           # Optional bundled scripts
        └── check.sh

Note: Not all of these files are required for a generalised hook implementation. In your own repository, hooks are stored as JSON files in .github/hooks/ (e.g., .github/hooks/my-hook.json). The folder structure above with README.md is specific to the Awesome Copilot repository for documentation purposes.

hooks.json

The configuration defines which events trigger which commands:

{
  "version": 1,
  "hooks": {
    "postToolUse": [
      {
        "type": "command",
        "bash": "npx prettier --write .",
        "cwd": ".",
        "timeoutSec": 30
      }
    ]
  }
}

Hook Events

Hooks can trigger on several lifecycle events:

EventWhen It FiresCommon Use Cases
sessionStartAgent session begins or resumesInitialize environments, log session starts, validate project state
sessionEndAgent session completes or is terminatedClean up temp files, generate reports, send notifications
userPromptSubmittedUser submits a promptLog requests for auditing and compliance
preToolUseBefore the agent uses any tool (e.g., bash, edit)Approve or deny tool executions, block dangerous commands, enforce security policies
postToolUseAfter a tool completes executionLog results, track usage, format code after edits, send failure alerts
agentStopMain agent finishes responding to a promptRun final linters/formatters, validate complete changes
subagentStopA subagent completes before returning resultsAudit subagent outputs, log subagent activity
errorOccurredAn error occurs during agent executionLog errors for debugging, send notifications, track error patterns

Key insight: The preToolUse hook is the most powerful — it can approve or deny individual tool executions. This enables fine-grained security policies like blocking specific shell commands or requiring approval for sensitive file operations.

Event Configuration

Each hook entry supports these fields:

{
  "type": "command",
  "bash": "./scripts/my-check.sh",
  "powershell": "./scripts/my-check.ps1",
  "cwd": ".",
  "timeoutSec": 10,
  "env": {
    "CUSTOM_VAR": "value"
  }
}

type: Always "command" for shell-based hooks.

bash: The command or script to execute on Unix systems. Can be inline or reference a script file.

powershell: The command or script to execute on Windows systems. Either bash or powershell (or both) must be provided.

cwd: Working directory for the command (relative to repository root).

timeoutSec: Maximum execution time in seconds (default: 30). The hook is killed if it exceeds this limit.

env: Additional environment variables merged with the existing environment.

README.md

The README provides metadata and documentation for the Awesome Copilot repository. While not required in your own implementation, it serves as a useful way to document them for your team.

---
name: 'Auto Format'
description: 'Automatically formats code using project formatters before commits'
tags: ['formatting', 'code-quality']
---

# Auto Format

Runs your project's configured formatter (Prettier, Black, gofmt, etc.)
automatically before the agent commits changes.

## Setup

1. Ensure your formatter is installed and configured
2. Copy the hooks.json to your `.github/hooks/` directory
3. Adjust the formatter command for your project

Practical Examples

Auto-Format After Edits

Ensure all code is formatted after the agent edits files:

{
  "version": 1,
  "hooks": {
    "postToolUse": [
      {
        "type": "command",
        "bash": "npx prettier --write . && git add -A",
        "cwd": ".",
        "timeoutSec": 30
      }
    ]
  }
}

Lint Check When Agent Completes

Run ESLint after the agent finishes responding and block if there are errors:

{
  "version": 1,
  "hooks": {
    "agentStop": [
      {
        "type": "command",
        "bash": "npx eslint . --max-warnings 0",
        "cwd": ".",
        "timeoutSec": 60
      }
    ]
  }
}

If the lint command exits with a non-zero status, the action is blocked.

Security Gating with preToolUse

Block dangerous commands before they execute:

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      {
        "type": "command",
        "bash": "./scripts/security-check.sh",
        "cwd": ".",
        "timeoutSec": 15
      }
    ]
  }
}

The preToolUse hook receives JSON input with details about the tool being called. Your script can inspect this input and exit with a non-zero code to deny the tool execution, or exit with zero to approve it.

Governance Audit

Scan user prompts for potential security threats and log session activity:

{
  "version": 1,
  "hooks": {
    "sessionStart": [
      {
        "type": "command",
        "bash": ".github/hooks/governance-audit/audit-session-start.sh",
        "cwd": ".",
        "timeoutSec": 5
      }
    ],
    "userPromptSubmitted": [
      {
        "type": "command",
        "bash": ".github/hooks/governance-audit/audit-prompt.sh",
        "cwd": ".",
        "env": {
          "GOVERNANCE_LEVEL": "standard",
          "BLOCK_ON_THREAT": "false"
        },
        "timeoutSec": 10
      }
    ],
    "sessionEnd": [
      {
        "type": "command",
        "bash": ".github/hooks/governance-audit/audit-session-end.sh",
        "cwd": ".",
        "timeoutSec": 5
      }
    ]
  }
}

This pattern is useful for enterprise environments that need to audit AI interactions for compliance.

Notification on Session End

Send a Slack or Teams notification when an agent session completes:

{
  "version": 1,
  "hooks": {
    "sessionEnd": [
      {
        "type": "command",
        "bash": "curl -X POST \"$SLACK_WEBHOOK_URL\" -H 'Content-Type: application/json' -d '{\"text\": \"Copilot agent session completed\"}'",
        "cwd": ".",
        "env": {
          "SLACK_WEBHOOK_URL": "${input:slackWebhook}"
        },
        "timeoutSec": 5
      }
    ]
  }
}

Writing Hook Scripts

For complex logic, use bundled scripts instead of inline bash commands:

#!/usr/bin/env bash
# scripts/pre-commit-check.sh
set -euo pipefail

echo "Running pre-commit checks..."

# Format code
npx prettier --write .

# Run linter
npx eslint . --fix

# Run type checker
npx tsc --noEmit

# Stage any formatting changes
git add -A

echo "Pre-commit checks passed ✅"

Tips for hook scripts:

  • Use set -euo pipefail to fail fast on errors
  • Keep scripts focused—one responsibility per script
  • Make scripts executable: chmod +x scripts/pre-commit-check.sh
  • Test scripts manually before adding them to hooks.json
  • Use reasonable timeouts—formatting a large codebase may need 30+ seconds

Best Practices

  • Keep hooks fast: Hooks run synchronously, so slow hooks delay the agent. Set tight timeouts and optimize scripts.
  • Use non-zero exit codes to block: If a hook exits with a non-zero code, the triggering action is blocked. Use this for must-pass checks.
  • Bundle scripts in the hook folder: Keep related scripts alongside the hooks.json for portability.
  • Document setup requirements: If hooks depend on tools being installed (Prettier, ESLint), document this in the README.
  • Test locally first: Run hook scripts manually before relying on them in agent sessions.
  • Layer hooks, don’t overload: Use multiple hook entries for independent checks rather than one monolithic script.

Common Questions

Q: Where do I put hooks configuration files?

A: Place them in the .github/hooks/ directory in your repository (e.g., .github/hooks/my-hook.json). You can have multiple hook files — all are loaded automatically. This makes hooks available to all team members.

Q: Can hooks access the user’s prompt text?

A: Yes, for userPromptSubmitted events the prompt content is available via JSON input to the hook script. Other hooks like preToolUse and postToolUse receive context about the tool being called. See the GitHub Copilot hooks documentation for details.

Q: What happens if a hook times out?

A: The hook is terminated and the agent continues. Set timeoutSec appropriately for your scripts.

Q: Can I have multiple hooks for the same event?

A: Yes. Hooks for the same event run in the order they appear in the array. If any hook fails (non-zero exit), subsequent hooks for that event may be skipped.

Q: Do hooks work with the Copilot coding agent?

A: Yes. Hooks are especially valuable with the coding agent because they provide deterministic guardrails for autonomous operations. See Using the Copilot Coding Agent for details.

Next Steps