A full audit trail for your GitHub Copilot agent sessions. Every prompt, response, and file change is captured and committed linearly to a configurable external git repo — giving you a complete record you can review, search, or use to reproduce sessions exactly as they happened.
Viewer dashboard at: https://github.com/alxayo/copilot-flight-recorder-viewer
- VS Code 1.99 or later with the GitHub Copilot Chat extension
- Git 2.20+ available on
PATH - jq (Linux/macOS only) — install guide
- PowerShell 5.1+ (Windows) or Bash 4+ (Linux/macOS)
- Operating systems: Windows, macOS, Linux
This hook system uses the VS Code Copilot Chat Hooks API, which relies on .github/hooks/copilot-audit.json and the specific hook events (SessionStart, UserPromptSubmit, PostToolUse, Stop) provided by the VS Code Copilot extension.
| Agent | Notes |
|---|---|
| GitHub Copilot Chat (VS Code) | Fully supported. All four hook events are used for complete audit coverage: session lifecycle, prompt capture, file-change diffs, and transcript export. |
| Agent | Why |
|---|---|
| GitHub Copilot CLI (standalone agent) | Has a hooks system that loads from .github/hooks/ — the same directory this project uses — but it is not a drop-in replacement. Key incompatibilities: (1) event key names differ (sessionStart vs SessionStart, userPromptSubmitted vs UserPromptSubmit, sessionEnd vs Stop); (2) no sessionId field in any payload — session identity would need to be derived from timestamp + cwd; (3) sessionEnd carries no transcript_path, so transcript capture is unavailable; (4) file-editing tool names differ (edit, create, bash vs create_file, replace_string_in_file, etc.), breaking the PostToolUse filtering logic. A CLI-specific hooks config and updated scripts could adapt the core git/audit approach. |
| Anthropic Claude Code | All four required events exist with identical names (SessionStart, UserPromptSubmit, PostToolUse, Stop), and session_id and transcript_path are common fields present in every event payload. However, it is not a drop-in replacement. Key incompatibilities: (1) hooks are configured in .claude/settings.json using a matcher-group structure with a command field, not in .github/hooks/copilot-audit.json with bash/powershell split keys; (2) all payload fields use snake_case (session_id, tool_name, tool_input) rather than camelCase — scripts must be updated accordingly; (3) file-editing tool names differ (Write, Edit vs create_file, replace_string_in_file, etc.), so the PostToolUse tool-name filter list must be rewritten; (4) no native PowerShell command path — a single command field runs in a shell, requiring a separate Windows adaptation strategy. The core git/audit logic could be adapted with a Claude Code-specific config and updated field parsing. |
| Cursor | Has a full hooks system configured in .cursor/hooks.json. All four conceptually equivalent events exist: sessionStart, beforeSubmitPrompt, postToolUse, and stop. Critically, transcript_path is a common base field present in every hook payload, and conversation_id serves as the stable session identifier. However, it is not a drop-in replacement. Key incompatibilities: (1) hooks are configured in .cursor/hooks.json with a single command field, not in .github/hooks/copilot-audit.json with bash/powershell split keys; (2) event names differ (sessionStart vs SessionStart, beforeSubmitPrompt vs UserPromptSubmit); (3) all payload fields use snake_case (conversation_id, tool_name, tool_input) — scripts must be updated accordingly; (4) file-editing tool names differ (Write vs create_file, replace_string_in_file, etc.), so the postToolUse filter list must be rewritten; (5) no native PowerShell command path — single command field requires a separate Windows adaptation strategy. The core git/audit approach is fully adaptable with a Cursor-specific config. |
| Agent | Why |
|---|---|
GitHub Copilot CLI extension (gh copilot) |
This is a separate product from the standalone Copilot CLI — a gh CLI extension for natural language shell command translation, not an agentic coding session runner. It has no hooks system and does not load .github/hooks/ configs. |
| Other editors/agents | Any tool that does not implement the VS Code Copilot Chat Hooks specification will not work with this system. |
Four VS Code agent hooks fire during a Copilot chat session:
| Hook Event | What It Does |
|---|---|
| SessionStart | Creates the session directory in the audit repo, commits a 000-session-start.md with metadata |
| UserPromptSubmit | Writes the user's prompt to NNN-prompt.md, commits it |
| PostToolUse | After a file-editing tool runs, captures git diff HEAD from the workspace and commits a NNN-changes.patch |
| Stop | Copies the full transcript (via transcript_path) to transcript.json, commits it |
Each file gets its own commit with a descriptive message like [<sessionId>] prompt: Fix the login bug….
- Flat (default): All commits go to a single branch (default
main). Commit messages include the session ID for filtering. - Per-session: Each chat session gets its own branch named
session/<sessionId>.
<audit-repo>/
└── sessions/
└── <sessionId>/
├── 001-session-start.md
├── 002-prompt.md
├── 003-changes.patch
├── 003-changes.meta.json
├── 004-prompt.md
├── 005-changes.patch
├── 005-changes.meta.json
└── transcript.json
-
Clone or copy this workspace (the one containing
.github/hooks/) into your project, or copy the.github/hooks/directory into an existing project's root. -
Create the audit repo (a separate git repo that will hold the audit trail):
mkdir ~/copilot-audit cd ~/copilot-audit git init git commit --allow-empty -m "init audit repo"
-
Configure by copying
.env.exampleto.envand setting at leastCOPILOT_AUDIT_REPO:cp .env.example .env # Edit .env: COPILOT_AUDIT_REPO=/home/you/copilot-auditAlternatively, set environment variables directly (they take priority over
.env). -
Open the workspace in VS Code. The hooks in
.github/hooks/copilot-audit.jsonare loaded automatically. -
Start a Copilot chat session. Check the GitHub Copilot Chat Hooks output channel to verify hooks are executing.
By default the audit trail lives in a separate git repository. If you prefer to keep everything in a single repo — your source code on main and audit data on an audit branch — you can use a git worktree.
The hooks need to git checkout the audit branch and commit files to it. If the audit repo is your main workspace directory, that checkout would switch your working tree away from your development branch, breaking your editor state and any in-progress work. A worktree gives you a second checkout directory backed by the exact same .git database, so both branches can be checked out simultaneously without interference.
Run these commands from your project root (e.g. C:\code\myproject):
# 1. Create an orphan "audit" branch with no files (keeps history separate from code)
git checkout --orphan audit
git rm -rf .
git commit --allow-empty -m "audit: init"
git checkout main # switch back to your working branch
# 2. Create a worktree directory so the audit branch has its own checkout
# Place it next to (not inside) your project directory
git worktree add ../myproject-audit auditOn Linux/macOS the commands are identical — just adjust the path style:
git worktree add ../myproject-audit auditThis creates ../myproject-audit/ checked out on the audit branch.
Point COPILOT_AUDIT_REPO at the worktree directory:
# .env (in your project root)
COPILOT_AUDIT_REPO=C:\code\myproject-audit # absolute path to the worktree
COPILOT_AUDIT_BRANCH=audit
COPILOT_AUDIT_MODE=flat
COPILOT_AUDIT_PUSH=falseOn Linux/macOS:
COPILOT_AUDIT_REPO=/home/you/code/myproject-auditmyproject/ ← your normal workspace (branch: main)
├── .git/ ← shared git database
├── .env ← points COPILOT_AUDIT_REPO to the worktree
├── .github/hooks/ ← copilot-flight-recorder hooks
└── src/ ← your source code
myproject-audit/ ← worktree checkout (branch: audit)
├── sessions/
│ └── <sessionId>/
│ ├── 001-session-start.md
│ ├── 002-prompt.md
│ ├── 003-changes.patch
│ └── transcript.json
- Both directories share the same
.gitdatabase, same remotes, same refs. - Commits made in either directory are immediately visible to the other.
- Branches are independent — updating
auditnever touchesmainand vice versa.
You can push the audit branch from either directory at any time, regardless of which branch is checked out in the other:
# From your main workspace
git push origin audit
# Or from the worktree
git -C C:\code\myproject-audit push origin auditTo push automatically after each session, set COPILOT_AUDIT_PUSH=true in your .env.
Since it's the same repo, all standard git commands work:
# See audit commits
git log audit --oneline
# Show a specific session's files
git show audit:sessions/<sessionId>/001-session-start.md
# Diff between two audit commits
git diff audit~5..auditIf you no longer need the separate checkout directory:
git worktree remove ../myproject-auditThe audit branch and all its commits remain in the repo. You can recreate the worktree at any time with git worktree add.
- Don't nest the worktree inside your project — place it alongside (e.g.
../myproject-audit) so that your workspace's.gitignoreand file watchers don't interfere with it. - The
auditbranch is an orphan branch with its own independent history. It shares no commits withmainand merging it is neither required nor recommended. - If you use per-session mode (
COPILOT_AUDIT_MODE=per-session), each session creates asession/<id>branch. These also branch off within the same repo and are visible everywhere. - Concurrent sessions: If multiple VS Code windows use the same worktree, commits may interleave. Use per-session mode or separate worktrees per workspace to avoid this.
All settings can be provided as environment variables or in a .env file at the workspace root. Environment variables take priority.
| Variable | Description | Default |
|---|---|---|
COPILOT_AUDIT_REPO |
Required. Absolute path to the audit git repo | — |
COPILOT_AUDIT_MODE |
flat or per-session |
flat |
COPILOT_AUDIT_BRANCH |
Branch name for flat mode | main |
COPILOT_AUDIT_PUSH |
Auto-push after session ends (true/false) |
false |
The PostToolUse hook only captures diffs when these VS Code tools are used:
create_filereplace_string_in_filemulti_replace_string_in_fileedit_notebook_fileinsert_text_in_filedelete_file
All other tools (terminal, search, etc.) are silently skipped.
Each PostToolUse event produces a .meta.json sidecar file alongside the .patch file, capturing workspace git state at the time of the edit:
{
"sessionId": "a1b2c3d4",
"filePath": "src/handler.ts",
"workspaceHead": "abc123def456",
"fileContentHash": "789abc012def",
"timestamp": "2026-03-11T10:30:00.000Z",
"toolName": "replace_string_in_file",
"patchFile": "003-changes.patch"
}| Field | Description |
|---|---|
workspaceHead |
git rev-parse HEAD of the source repo at edit time — the baseline commit the .patch diff is relative to |
fileContentHash |
git hash-object of the file after the edit — a content-addressable hash even without a commit. null for file deletions |
patchFile |
Name of the companion .patch file containing the actual diff |
Copilot edits files in the working tree without committing. When the user eventually commits (e.g. xyz789), its parent is the workspaceHead recorded in the metadata. To find all Copilot-driven edits that became part of a commit:
# Find the parent of a source commit
PARENT=$(git rev-parse xyz789~1)
# Find all audit metadata referencing that baseline
jq -s "[.[] | select(.workspaceHead == \"$PARENT\")]" \
sessions/*/???-changes.meta.json# All files changed by a specific session
jq -s '[.[] | select(.sessionId == "a1b2c3d4") | .filePath]' \
sessions/*/???-changes.meta.json
# All sessions that touched a specific file
jq -s '[.[] | select(.filePath | endswith("handler.ts")) | .sessionId] | unique' \
sessions/*/???-changes.meta.json
# Full cross-reference table
jq -s '[.[] | {sessionId, filePath, workspaceHead, toolName}]' \
sessions/*/???-changes.meta.json- Set
COPILOT_AUDIT_REPOto a test git repo and open the workspace in VS Code. - Start a chat session → check for
001-session-start.mdin the audit repo. - Send a prompt → check for
002-prompt.md. - Ask Copilot to edit a file → check for
003-changes.patch. - End the session → check for
transcript.json. - For per-session mode, set
COPILOT_AUDIT_MODE=per-sessionand verify asession/<id>branch is created.
- The
.envfile is git-ignored to prevent leaking local paths. - Hook scripts run with the same permissions as VS Code. Review them before use in shared repos.
- Consider using
chat.tools.edits.autoApproveto prevent the agent from modifying hook scripts during a session. - No secrets are stored in scripts — all config flows through environment variables.
git diff HEADcaptures cumulative workspace changes, not per-tool incremental diffs. If you make manual edits between tool uses, those appear in the patch too.- Brand-new untracked files (from
create_file) are captured as raw content rather than unified diff format. - No file-locking: concurrent sessions targeting the same audit repo could interleave commits. Use per-session mode or separate audit repos to avoid this.
copilot-flight-recorder is packaged as a VS Code Agent Plugin and can be installed in several ways.
Prerequisite: Enable agent plugins in VS Code with
"chat.plugins.enabled": true.
- Download the latest
.zipor.tar.gzfrom Releases. - Extract to a directory (e.g.
~/.copilot-plugins/copilot-flight-recorder). - Add to your VS Code
settings.json:"chat.plugins.paths": { "/path/to/copilot-flight-recorder": true }
git clone https://github.com/alxayo/copilot-flight-recorder.git ~/.copilot-plugins/copilot-flight-recorderThen add the path to chat.plugins.paths as shown above.
Add the repo directly as a plugin marketplace in your VS Code settings.json:
"chat.plugins.marketplaces": ["alxayo/copilot-flight-recorder"]VS Code will discover and offer the plugin for installation automatically.
From a cloned copy of this repo:
# Linux / macOS
./scripts/install-plugin.sh
# Windows (PowerShell)
.\scripts\install-plugin.ps1The script copies the plugin to ~/.copilot-plugins/copilot-flight-recorder and prints the settings.json snippet to activate it.
To create distributable archives from source:
# Linux / macOS
bash scripts/build-plugin.sh
# Windows (PowerShell)
powershell -ExecutionPolicy Bypass -File scripts\build-plugin.ps1This produces dist/copilot-flight-recorder-<version>.zip and .tar.gz archives ready for distribution.
A GitHub Actions workflow (.github/workflows/release-plugin.yml) automatically:
- Validates the plugin structure (all required files, valid JSON)
- Builds the zip and tar.gz archives
- Publishes them as GitHub Release assets when you push a version tag:
git tag v1.0.0
git push origin v1.0.0