# Using Copilot SDK Hooks for Audit Logging and Compliance

> Leverage Copilot SDK hooks for robust audit logging and compliance. Build immutable audit trails and enforce policies by intercepting session events. Learn how in this guide.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The Copilot SDK provides a session-hooks framework that intercepts every major lifecycle event—such as tool execution and session start/end—allowing you to build immutable audit trails and enforce compliance policies via the `SessionHooks` trait.**

The Copilot SDK exposes a comprehensive session-hooks framework that lets you intercept every major event in a Copilot session. By implementing the `SessionHooks` trait or its language-specific equivalents, you can record tool invocations, mask sensitive data, and deny unauthorized operations in real time. This capability enables organizations to satisfy strict audit and compliance requirements while maintaining full visibility into AI-assisted workflows.

## How the Session Hooks Framework Works

The core dispatch logic resides in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), where the `dispatch_hook` function orchestrates the hook invocation pipeline. According to the GitHub Copilot SDK source code, the dispatcher performs four critical steps:

1. Deserializes the raw JSON payload into a typed input structure (e.g., `PreToolUseInput`, `PostToolUseInput`).
2. Wraps the input together with a `HookContext` containing a unique `sessionId`.
3. Invokes the user-implemented `SessionHooks::on_hook` method (or language-specific equivalents like `onPreToolUse`).
4. Serializes the returned `HookOutput` back to JSON for the CLI.

Each hook receives a **typed input structure** plus the shared `HookContext`. The `sessionId` serves as the correlation key, allowing you to group disparate events—from tool calls to session termination—into a single audit log entry.

## Lifecycle Hooks for Comprehensive Audit Coverage

The `SessionHooks` interface exposes specific callbacks that map to critical points in the Copilot session lifecycle. You can implement any combination of these hooks to capture the precise data your compliance framework requires:

- **`onPreToolUse`** – Fires immediately before a tool executes. Use this to record the requested tool name, arguments, and enforce permission policies.
- **`onPostToolUse`** – Fires after a tool succeeds. Capture results, mask secrets, or append additional context before the data persists to your audit store.
- **`onPostToolUseFailure`** – Fires when a tool returns an error. Log failure details and optionally inject remediation guidance.
- **`onUserPromptSubmitted`** – Fires when a user submits a prompt. Persist both the original and transformed prompt text for complete traceability.
- **`onSessionStart` / `onSessionEnd`** – Bookend events for session-wide metadata (user ID, purpose, start/end timestamps) and cleanup operations.
- **`onErrorOccurred`** – Captures CLI-level errors for audit trails and retry policy decisions.
- **`onAgentStop`** – Triggers when the top-level agent reaches a natural stop, allowing final compliance checks before termination.

For a complete walkthrough of the audit use case, see the features/hooks documentation in [`docs/features/hooks.md`](https://github.com/github/copilot-sdk/blob/main/docs/features/hooks.md), which demonstrates how to aggregate these events into a single immutable record.

## Enforcing Compliance Policies Through Hook Outputs

The Copilot SDK hooks enforce compliance through structured output variants that modify session behavior:

**Permission Decisions** – In `onPreToolUse`, return `permissionDecision: "deny"` with a reason string to abort the tool call before side effects occur. The SDK prevents the execution immediately upon receiving this output.

**Redaction and Masking** – In `onPostToolUse`, return a `modifiedResult` to replace sensitive tool output before it reaches the user or log files. You can also add `additionalContext` that is sanitized separately from the original data.

**Type Safety and Validation** – The Rust implementation in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs) validates that the output variant matches the invoked hook type. If a hook returns an unexpected structure, the framework treats it as "no hook registered," preventing malformed data from leaking into the audit stream.

## Complete Implementation Examples

Each supported language follows the same pattern: create the client, implement callbacks to append entries to an in-memory array, and write the array to `audit-<sessionId>.json` when the session ends.

### TypeScript / Node.js

```typescript
import { CopilotClient } from "@github/copilot-sdk";

type AuditEntry = {
  timestamp: number;
  event: string;
  details: Record<string, unknown>;
};

const auditLog: AuditEntry[] = [];

const client = new CopilotClient();

const session = await client.createSession({
  hooks: {
    onPreToolUse: async (input) => {
      auditLog.push({
        timestamp: Date.now(),
        event: "preToolUse",
        details: {
          toolName: input.toolName,
          toolArgs: input.toolArgs,
        },
      });
      // Allow all tools; change to "deny" for compliance policies.
      return { permissionDecision: "allow" };
    },

    onPostToolUse: async (input) => {
      auditLog.push({
        timestamp: Date.now(),
        event: "postToolUse",
        details: {
          toolName: input.toolName,
          result: input.toolResult,
        },
      });
      return null; // No modification needed.
    },

    onSessionEnd: async (input) => {
      // Persist the audit log for the session.
      const fs = await import("fs/promises");
      await fs.writeFile(
        `audit-${input.sessionId}.json`,
        JSON.stringify(auditLog, null, 2)
      );
      return null;
    },
  },
});

```

Source: [`docs/hooks/hooks-overview.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/hooks-overview.md)

### Python

```python
import json, aiofiles
from copilot import CopilotClient

audit_log = []

async def on_pre_tool_use(input_data, _):
    audit_log.append({
        "ts": int(input_data["timestamp"]),
        "event": "preToolUse",
        "tool": input_data["toolName"],
        "args": input_data["toolArgs"],
    })
    return {"permissionDecision": "allow"}

async def on_post_tool_use(input_data, _):
    audit_log.append({
        "ts": int(input_data["timestamp"]),
        "event": "postToolUse",
        "tool": input_data["toolName"],
        "result": input_data["toolResult"],
    })
    return None

async def on_session_end(input_data, _):
    async with aiofiles.open(f"audit-{input_data['sessionId']}.json", "w") as f:
        await f.write(json.dumps(audit_log, indent=2))

client = CopilotClient()
await client.start()
await client.create_session(
    on_pre_tool_use=on_pre_tool_use,
    on_post_tool_use=on_post_tool_use,
    on_session_end=on_session_end,
)

```

Source: [`docs/hooks/hooks-overview.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/hooks-overview.md)

### Go

```go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"time"

	copilot "github.com/github/copilot-sdk/go"
)

type AuditEntry struct {
	Timestamp int64                  `json:"timestamp"`
	Event     string                 `json:"event"`
	Details   map[string]interface{} `json:"details"`
}

var auditLog []AuditEntry

func main() {
	client := copilot.NewClient(nil)

	session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
		Hooks: &copilot.SessionHooks{
			OnPreToolUse: func(input copilot.PreToolUseHookInput, _ copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) {
				auditLog = append(auditLog, AuditEntry{
					Timestamp: time.Now().UnixMilli(),
					Event:     "preToolUse",
					Details: map[string]any{
						"toolName": input.ToolName,
						"args":     input.ToolArgs,
					},
				})
				return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil
			},
			OnPostToolUse: func(input copilot.PostToolUseHookInput, _ copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) {
				auditLog = append(auditLog, AuditEntry{
					Timestamp: time.Now().UnixMilli(),
					Event:     "postToolUse",
					Details: map[string]any{
						"toolName":  input.ToolName,
						"toolResult": input.ToolResult,
					},
				})
				return nil, nil
			},
			OnSessionEnd: func(input copilot.SessionEndHookInput, _ copilot.HookInvocation) (*copilot.SessionEndHookOutput, error) {
				f, _ := os.Create(fmt.Sprintf("audit-%s.json", input.SessionId))
				enc := json.NewEncoder(f)
				enc.SetIndent("", "  ")
				_ = enc.Encode(auditLog)
				return nil, nil
			},
		},
	})
	_ = session // use the session as needed
}

```

Source: [`docs/hooks/hooks-overview.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/hooks-overview.md)

### C# (.NET)

```csharp
using GitHub.Copilot;
using System.Text.Json;

var client = new CopilotClient();

var auditLog = new List<object>();

var session = await client.CreateSessionAsync(new SessionConfig
{
    Hooks = new SessionHooks
    {
        OnPreToolUse = (input, _) =>
        {
            auditLog.Add(new
            {
                timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                @event = "preToolUse",
                tool = input.ToolName,
                args = input.ToolArgs
            });
            return Task.FromResult<PreToolUseHookOutput?>(
                new PreToolUseHookOutput { PermissionDecision = "allow" });
        },

        OnPostToolUse = (input, _) =>
        {
            auditLog.Add(new
            {
                timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                @event = "postToolUse",
                tool = input.ToolName,
                result = input.ToolResult
            });
            return Task.FromResult<PostToolUseHookOutput?>(null);
        },

        OnSessionEnd = (input, _) =>
        {
            var path = $"audit-{input.SessionId}.json";
            var json = JsonSerializer.Serialize(auditLog, new JsonSerializerOptions { WriteIndented = true });
            return System.IO.File.WriteAllTextAsync(path, json).ContinueWith(_ => (SessionEndHookOutput?)null);
        }
    }
});

```

Source: [`docs/hooks/hooks-overview.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/hooks-overview.md)

### Java

```java
import com.github.copilot.*;
import com.github.copilot.rpc.*;
import java.nio.file.*;
import java.util.*;

try (var client = new CopilotClient()) {
    client.start().get();

    var auditLog = new ArrayList<Map<String, Object>>();

    var hooks = new SessionHooks()
        .setOnPreToolUse((input, _) -> {
            auditLog.add(Map.of(
                "timestamp", System.currentTimeMillis(),
                "event", "preToolUse",
                "tool", input.getToolName(),
                "args", input.getToolArgs()
            ));
            return CompletableFuture.completedFuture(PreToolUseHookOutput.allow());
        })
        .setOnPostToolUse((input, _) -> {
            auditLog.add(Map.of(
                "timestamp", System.currentTimeMillis(),
                "event", "postToolUse",
                "tool", input.getToolName(),
                "result", input.getToolResult()
            ));
            return CompletableFuture.completedFuture(null);
        })
        .setOnSessionEnd((input, _) -> {
            var path = Paths.get("audit-" + input.getSessionId() + ".json");
            var json = new com.google.gson.GsonBuilder().setPrettyPrinting().create()
                         .toJson(auditLog);
            return CompletableFuture.runAsync(() -> {
                try { Files.writeString(path, json); } catch (Exception e) {}
            });
        });

    var session = client.createSession(
        new SessionConfig()
            .setHooks(hooks)
            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
    ).get();
}

```

Source: [`docs/hooks/hooks-overview.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/hooks-overview.md)

## Summary

Implementing audit logging with Copilot SDK hooks provides tamper-evident compliance tracking across your AI-assisted workflows:

- **Intercept every critical event** using the `SessionHooks` trait and its language-specific implementations.
- **Correlate events by session** using the `sessionId` provided in every `HookContext`.
- **Enforce policies preemptively** by returning `permissionDecision: "deny"` from `onPreToolUse`.
- **Sanitize sensitive data** by modifying results in `onPostToolUse` before persistence.
- **Validate output safety** through the Rust dispatcher's type checking in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), which prevents malformed hook outputs from affecting the session.

## Frequently Asked Questions

### How do I correlate multiple events to a single audit log entry?

Every hook receives a `HookContext` object containing a unique `sessionId`. Group your audit records by this identifier to produce a single JSON file per session, as shown in the `onSessionEnd` examples that write to `audit-<sessionId>.json`.

### Can I block a tool from executing based on compliance rules?

Yes. Implement `onPreToolUse` and return an output structure with `permissionDecision: "deny"` and a reason string. According to the SDK source code in [`rust/src/hooks.rs`](https://github.com/github/copilot-sdk/blob/main/rust/src/hooks.rs), the dispatcher aborts the tool call immediately upon receiving this output, preventing any side effects.

### How do I prevent sensitive data from appearing in audit logs?

Use the `onPostToolUse` hook to inspect the `toolResult` before it is logged. Return a `modifiedResult` field in your hook output to replace the sensitive content with redacted values or tokens. You can also append sanitized data via `additionalContext` while omitting the original result from your internal audit array.

### What happens if my hook returns the wrong output type?

The Rust implementation validates that the returned `HookOutput` variant matches the invoked hook type. If a type mismatch occurs, the framework treats the invocation as if no hook were registered, ensuring that malformed outputs do not leak into the CLI or compromise the audit trail.