# Copilot SDK Hooks: How to Customize Session Behavior with Interceptors

> Customize Copilot SDK session behavior using extensibility points called hooks. Intercept lifecycle events like tool execution and prompt submission to enforce permissions and transform data.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: deep-dive
- Published: 2026-07-18

---

**Copilot SDK Hooks are extensibility points that let hosts intercept lifecycle events—such as tool execution and prompt submission—to enforce permissions, transform data, and inject hidden context without modifying the core Copilot runtime.**

Copilot SDK Hooks provide a powerful mechanism for customizing how GitHub Copilot sessions behave in VS Code extensions, CLI tools, and other host applications. These interceptors allow developers to implement organization-specific policies, audit logging, and dynamic UI adjustments by registering handler functions that execute at specific points in the session lifecycle. By leveraging the `@github/copilot-sdk` package, you can control everything from tool permissions to prompt rewriting using typed input and output contracts.

## What Are Copilot SDK Hooks?

Copilot SDK Hooks are a set of asynchronous callback functions that the SDK invokes during critical moments of a Copilot session. When you create a session via `CopilotClient.createSession()`, you provide a **`SessionHooks`** object that maps hook types—such as `preToolUse` or `sessionStart`—to your custom handler implementations.

Each hook receives a typed **`*HookInput`** object (extending `BaseHookInput`) containing contextual data like `sessionId`, `timestamp`, and `workingDirectory`. Your handler can optionally return a **`*HookOutput`** object that modifies the session flow, such as denying a tool call or appending hidden context for the LLM. The SDK's internal dispatcher, `CopilotSession._handleHooksInvoke`, manages the RPC communication between the runtime and your handlers using `HookInvokeRequest` and `HookInvokeResponse` envelopes defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts).

## How Hooks Customize Session Behavior

Hook handlers influence sessions through four primary mechanisms:

- **Permission Decisions** — Return `permissionDecision: 'deny'` to block tool execution, or `'ask'` to require user confirmation.
- **Argument and Result Transformation** — Modify tool arguments before execution or alter results before they reach the model.
- **Additional Hidden Context** — Inject guidance text that the LLM receives but the user never sees, useful for recovery from failures.
- **Output Suppression** — Hide tool results from the UI while still allowing the model to process them.

These capabilities enable use cases ranging from security policy enforcement to automated debugging assistance without forking the Copilot SDK.

## Available Hook Types

The Copilot SDK defines seven distinct hook points in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts):

| Hook | When It Fires | Input Type | Customization Options |
|------|---------------|------------|----------------------|
| **preToolUse** | Before a tool executes | `PreToolUseHookInput` | Deny/allow tool, modify arguments, add context |
| **postToolUse** | After successful tool completion | `PostToolUseHookInput` | Transform results, append metadata |
| **postToolUseFailure** | After tool returns failure | `PostToolUseFailureHookInput` | Add recovery guidance via `additionalContext` |
| **userPromptSubmitted** | When user sends a message | `UserPromptSubmittedHookInput` | Rewrite prompt, inject system context |
| **sessionStart** | On session creation | `SessionStartHookInput` | Provide initial session-wide context |
| **sessionEnd** | On session close/disconnect | `SessionEndHookInput` | Execute cleanup logic, log final state |
| **errorOccurred** | When internal errors bubble up | `ErrorOccurredHookInput` | Record diagnostics, transform error messages |

All input types inherit from `BaseHookInput`, ensuring consistent access to session metadata across every hook handler.

## Implementing Copilot SDK Hooks

### Registering Hooks During Session Creation

Define your handlers when calling `createSession()` on a `CopilotClient` instance. The following example demonstrates registering multiple hooks to enforce policies and augment context:

```typescript
import { CopilotClient, CopilotSession } from '@github/copilot-sdk';

const client = new CopilotClient({ /* configuration */ });

const hooks = {
  // Block dangerous tools
  preToolUse: async (input) => {
    if (input.toolName === 'dangerous') {
      return { 
        permissionDecision: 'deny', 
        permissionDecisionReason: 'Policy violation: dangerous tool blocked' 
      };
    }
  },

  // Log and prepend reminders to user prompts
  userPromptSubmitted: async (input) => {
    console.log('Processing prompt:', input.prompt);
    return { modifiedPrompt: `[Internal User] ${input.prompt}` };
  },

  // Provide system context at session start
  sessionStart: async () => ({
    additionalContext: 'You are assisting an internal development team.'
  })
};

const session: CopilotSession = await client.createSession({ hooks });

```

*Source:* [`nodejs/test/e2e/hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks.e2e.test.ts) demonstrates similar registration patterns.

### Controlling Tool Execution with preToolUse

The `preToolUse` hook is the primary control point for tool governance. It receives the tool name and arguments before execution, allowing you to implement conditional logic:

```typescript
it('should block denied tools via preToolUse', async () => {
  const hookLog: string[] = [];

  const session = await client.createSession({
    hooks: {
      preToolUse: async (input) => {
        hookLog.push(`pre:${input.toolName}`);
        if (input.toolName === 'blocked') {
          return { 
            permissionDecision: 'deny', 
            permissionDecisionReason: 'Blocked by security policy' 
          };
        }
      }
    }
  });

  const result = await session.runTool('blocked', { arg: 'value' });
  expect(result).toBeUndefined();           // Tool never executed
  expect(hookLog).toContain('pre:blocked'); // Hook was invoked
});

```

When the handler returns `permissionDecision: 'deny'`, the SDK prevents the tool from running and returns `undefined` to the caller, as verified in [`nodejs/test/e2e/hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks.e2e.test.ts).

### Handling Session Lifecycle Events

Use `sessionStart` to initialize resources and `sessionEnd` to guarantee cleanup, even if the session terminates unexpectedly:

```typescript
import { promises as fs } from 'fs';
import { join } from 'path';

const tempDir = await fs.mkdtemp('/tmp/copilot-session-');

const session = await client.createSession({
  hooks: {
    sessionStart: async () => {
      console.log('Session initialized:', tempDir);
      return { additionalContext: 'Working in temporary workspace.' };
    },
    
    sessionEnd: async () => {
      await fs.rm(tempDir, { recursive: true, force: true });
      console.log('Cleanup completed');
    }
  }
});

// Session work occurs here...

await session.dispose(); // Triggers sessionEnd hook

```

The `sessionEnd` hook fires when `session.dispose()` is called or the connection drops, making it ideal for resource management patterns shown in [`nodejs/test/e2e/hooks_extended.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks_extended.e2e.test.ts).

## Architecture and Dispatch Mechanism

When the Copilot runtime emits a hook event, it serializes the data into a `HookInvokeRequest` and transmits it over the RPC channel defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts). The SDK's `CopilotSession._handleHooksInvoke` method receives this request, matches the string event name (e.g., `"preToolUse"`, `"postToolUseFailure"`) to the corresponding handler in the session's `hooks` map, and awaits the asynchronous handler.

The handler receives a strongly typed input object—such as `PreToolUseHookInput` or `UserPromptSubmittedHookInput`—defined in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts). If the handler returns an object, the dispatcher wraps it in a `HookInvokeResponse` and applies the modifications to the ongoing session. The unit tests in [`nodejs/test/client.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/client.test.ts) verify this dispatcher logic, ensuring that string-typed events correctly map to their respective handler signatures.

## Summary

- **Copilot SDK Hooks** provide interceptors for seven session lifecycle events, from tool execution to error handling.
- **Handlers** receive typed `*HookInput` objects and return optional `*HookOutput` objects to modify behavior.
- **Permission controls**, argument transformation, and hidden context injection occur through the `preToolUse`, `postToolUse`, and `userPromptSubmitted` hooks.
- **Type definitions** reside in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts), while the RPC wire format lives in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts).
- **Cleanup and initialization** logic belongs in `sessionStart` and `sessionEnd` hooks to ensure reliable resource management.

## Frequently Asked Questions

### What is the difference between preToolUse and postToolUse hooks?

The **`preToolUse`** hook fires before the tool executes and can prevent execution entirely by returning `permissionDecision: 'deny'` or modify arguments before they reach the tool. The **`postToolUse`** hook fires after successful completion, allowing you to transform the result or append metadata before the model receives it. Use `preToolUse` for security policy enforcement and `postToolUse` for result formatting or logging.

### How do I deny a tool execution using Copilot SDK Hooks?

Return an object with `permissionDecision: 'deny'` and an optional `permissionDecisionReason` from your `preToolUse` handler. When the SDK dispatcher processes this output, it prevents the tool from running and returns `undefined` instead of a result. This pattern appears in [`nodejs/test/e2e/hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks.e2e.test.ts) where tools named `"blocked"` are rejected programmatically.

### Can I modify the user's prompt before it reaches the model?

Yes. Implement the **`userPromptSubmitted`** hook and return a `modifiedPrompt` field in your output object. This allows you to prepend system instructions, sanitize sensitive data, or rewrite queries entirely. The modified prompt replaces the original in the message history sent to the LLM, while the original remains available in the hook input for logging purposes.

### Where are hook types defined in the Copilot SDK source code?

All hook input and output interfaces—such as `PreToolUseHookInput`, `PostToolUseFailureHookInput`, and `BaseHookInput`—are defined in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts). The RPC transport contracts (`HookInvokeRequest`, `HookInvokeResponse`) are generated in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts). Reference these files when implementing custom handlers to ensure type safety across your interceptors.