# How to Allow-List Specific Tools Using the onPreToolUse Hook in Copilot SDK

> Allow-list specific tools in Copilot SDK using the onPreToolUse hook. Learn to control tool access by inspecting tool names and arguments to grant or deny permissions.

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

---

**You can allow-list specific tools by registering an `onPreToolUse` hook when calling `client.createSession`, inspecting `toolName` (and optionally `toolArgs`) in the `PreToolUseHookInput` payload, and returning a `PreToolUseHookOutput` with `permissionDecision` set to `"allow"` or `"deny"`.**

The GitHub Copilot SDK enables runtime gatekeeping of model-invoked tools through its **pre-tool-use hook** architecture. By implementing the `onPreToolUse` handler, you can selectively allow-list specific tools on a per-call basis rather than relying solely on the static `availableTools` session option. This guide walks through the hook-based approach using the actual type definitions and dispatcher logic found in the `github/copilot-sdk` repository.

## How to Allow-List Specific Tools at Runtime

Before a tool executes, the SDK constructs a **`PreToolUseHookInput`** payload containing the `toolName`, `toolArgs`, and contextual metadata. This payload is dispatched to your registered handler via **`Session.handleHook`** in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) (lines 1078–1085), which awaits your **`PreToolUseHookOutput`** response. The corresponding I/O types are declared in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts) (lines 1072–1088), where the output shape requires a `permissionDecision` field that must be `"allow"`, `"deny"`, or `"ask"`.

If the handler returns `"deny"`, the runtime aborts the tool call before any side effects occur; the model receives a blocked-tool message instead of execution results. This behavior is validated in the end-to-end suite at [`nodejs/test/e2e/hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/hooks.e2e.test.ts) (lines 19–30). Unlike the static `availableTools` list applied at session creation—documented in [`docs/setup/multi-tenancy.md`](https://github.com/github/copilot-sdk/blob/main/docs/setup/multi-tenancy.md) (line 415)—the hook evaluates every call dynamically.

## Basic Hard-Coded Allow-List

The simplest way to allow-list specific tools is to compare `input.toolName` against a static set of permitted tools inside the hook.

```typescript
import { CopilotClient, approveAll } from "@github/copilot-sdk";
import type {
  PreToolUseHookInput,
  PreToolUseHookOutput,
} from "@github/copilot-sdk/src/index.js";

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

const ALLOWED_TOOLS = new Set(["read_file", "write_file"]);

await client.createSession({
  onPermissionRequest: approveAll,
  hooks: {
    onPreToolUse: async (input: PreToolUseHookInput): Promise<PreToolUseHookOutput> => {
      console.log(`Tool request: ${input.toolName}`);

      if (ALLOWED_TOOLS.has(input.toolName)) {
        return { permissionDecision: "allow" };
      }

      return {
        permissionDecision: "deny",
        permissionDecisionReason: "tool not permitted",
      };
    },
  },
});

```

In this pattern, the **`ALLOWED_TOOLS`** set acts as the authoritative registry. Because the hook runs for every tool invocation, returning `{ permissionDecision: "deny" }` immediately prevents disallowed tools from running.

## Conditional Allow-Lists Based on Tool Arguments

For policies that depend on payload contents, inspect **`input.toolArgs`** before rendering a decision. The following example permits the `search_documents` tool only when the query string is 200 characters or fewer.

```typescript
const SESSION_ALLOWED_TOOL = "search_documents";

await client.createSession({
  onPermissionRequest: approveAll,
  hooks: {
    onPreToolUse: async (input) => {
      if (input.toolName === SESSION_ALLOWED_TOOL) {
        const args = input.toolArgs as { query: string };

        if (args.query.length <= 200) {
          return { permissionDecision: "allow" };
        }

        return {
          permissionDecision: "deny",
          permissionDecisionReason: "query too long",
        };
      }

      return { permissionDecision: "deny" };
    },
  },
});

```

Your hook can also sanitize arguments rather than rejecting the call. Return **`modifiedArgs`** in the output object to overwrite values before the tool executes:

```typescript
return {
  permissionDecision: "allow",
  modifiedArgs: { query: args.query.slice(0, 200) },
};

```

## Dynamic Allow-Lists from External Data Sources

Because the hook is asynchronous, you can fetch live policies from a database or remote service.

```typescript
async function fetchAllowedToolsForUser(userId: string): Promise<Set<string>> {
  const rows = await db.query(
    "SELECT tool_name FROM allowed_tools WHERE user_id = ?",
    [userId]
  );
  return new Set(rows.map((r) => r.tool_name));
}

await client.createSession({
  onPermissionRequest: approveAll,
  hooks: {
    onPreToolUse: async (input, inv) => {
      const allowed = await fetchAllowedToolsForUser(inv.sessionId);
      return {
        permissionDecision: allowed.has(input.toolName) ? "allow" : "deny",
      };
    },
  },
});

```

Here, the second argument (`inv`) exposes session metadata such as `sessionId`, letting you resolve per-user or per-organization permissions at runtime. This is the recommended pattern for multi-tenant applications that cannot rely on a static configuration.

## Summary

- Register the **`onPreToolUse`** function inside the `hooks` object passed to `client.createSession`.
- Inspect **`input.toolName`** and **`input.toolArgs`** in your handler to enforce allow-list policies.
- Return **`permissionDecision: "allow"`** to permit execution, or **`"deny"`** to block the tool before side effects occur.
- Use **`modifiedArgs`** to sanitize inputs instead of rejecting the call entirely.
- The hook dispatcher is implemented in `Session.handleHook` at **[`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts)** (lines 1078–1085), and the I/O types are declared in **[`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts)** (lines 1072–1088).

## Frequently Asked Questions

### What is the difference between `availableTools` and the `onPreToolUse` hook?

The **`availableTools`** option is a static allow-list evaluated once when the session is created. The **`onPreToolUse`** hook runs at runtime before every individual tool invocation, allowing you to enforce dynamic policies based on the current conversation state, user identity, or argument content.

### Can I modify tool arguments instead of denying the request?

Yes. Your hook can return **`modifiedArgs`** inside the `PreToolUseHookOutput` object. The SDK applies these mutations before executing the tool, which is useful for sanitizing inputs, truncating strings, or injecting default values.

### Does the hook run synchronously or asynchronously?

The hook runs **asynchronously**. According to the implementation in `github/copilot-sdk`, the handler may perform asynchronous operations such as database queries or remote API calls before returning the permission decision.

### Where is the hook invoked in the Copilot SDK source code?

The hook is invoked from **`Session.handleHook`** in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) (lines 1078–1085). This dispatcher deserializes the `PreToolUseHookInput`, calls the registered `onPreToolUse` handler, and routes the resulting `PreToolUseHookOutput` back to the runtime to either proceed with or abort the tool call.