# How to Define a PreToolUse Hook in Claude Code: A Complete Implementation Guide

> Learn to define a PreToolUse hook in Claude Code. Execute custom JavaScript before tool invocation by registering an event and exporting an async run function to modify tool calls and session state.

- Repository: [WorldFlowAI/everything-claude-code](https://github.com/WorldFlowAI/everything-claude-code)
- Tags: how-to-guide
- Published: 2026-09-07

---

**PreToolUse hooks in Claude Code execute custom JavaScript logic immediately before any tool invocation by registering a script in [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) with `"event": "preToolUse"` and exporting an async `run(context)` function that receives and can modify the tool name, arguments, and session state.**

Hooks are a core extension mechanism in the `WorldFlowAI/everything-claude-code` repository. The **PreToolUse hook** specifically intercepts tool calls at the moment Claude Code is about to execute any tool—whether built-in (file read, search) or custom. This capability enables argument validation, security filtering, logging, and dynamic transformation of tool inputs before execution proceeds.

## PreToolUse Hook Architecture

The hook system follows a three-layer design implemented across the repository's `scripts/hooks/`, `hooks/`, and `tests/hooks/` directories.

### Core Components

| Component | Location | Purpose |
|-----------|----------|---------|
| **Hook script** | `scripts/hooks/*.js` | Contains the executable logic; must export `run(context)` |
| **Hook manifest** | [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) | Registers when the hook fires via `"event": "preToolUse"` |
| **Test suite** | [`tests/hooks/hooks.test.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/tests/hooks/hooks.test.js) | Validates hook behavior and side effects |

### Execution Flow

```

User Request → Claude Code → PreToolUse Hook → Tool Execution → Response
                    ↑_______________|
                         (mutated context returned)

```

When a tool call occurs, Claude Code queries [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) for entries matching `"event": "preToolUse"`, loads the specified script, and awaits its `run` function. The hook may return a modified `context` object to alter tool behavior or throw an error to abort execution entirely.

## Creating a PreToolUse Hook: Step-by-Step

### Step 1: Implement the Hook Script

Create a JavaScript file in `scripts/hooks/` that exports an async `run` function. The function receives a `context` object with these properties:

- **`context.tool`** (`string`): Name of the tool about to execute
- **`context.args`** (`Array`): Arguments originally passed to the tool
- **`context.session`** (`Object`): Current session state object

```javascript
// scripts/hooks/my-pre-tool.js
/**
 * PreToolUse hook for Claude Code.
 * Executes before any tool invocation to validate or transform inputs.
 * 
 * @param {Object} context - Execution context
 * @param {string} context.tool - Tool name being invoked
 * @param {Array} context.args - Tool arguments
 * @param {Object} context.session - Mutable session state
 * @returns {Promise<Object>} - Modified or original context
 */
export async function run(context) {
  // Block dangerous tools by name
  const blockedTools = ['dangerousTool', 'unauthorizedExec'];
  if (blockedTools.includes(context.tool)) {
    throw new Error(`PreToolUse hook blocked: "${context.tool}" is not allowed`);
  }

  // Transform arguments: prefix all strings with execution marker
  context.args = context.args.map(arg => 
    typeof arg === 'string' ? `[AUDITED] ${arg}` : arg
  );

  // Log tool invocations for audit trail
  console.error(`[PreToolUse] ${context.tool}: ${JSON.stringify(context.args)}`);

  // Return mutated context to propagate changes
  return context;
}

```

The `run` function must be exported as a named export. The repository's hook loader in [`scripts/hooks/pre-compact.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/scripts/hooks/pre-compact.js) demonstrates similar mutation patterns for session state management.

### Step 2: Register in hooks.json

Add an entry to [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) with the exact `"preToolUse"` event identifier. The `"script"` path is relative to the repository root.

```json
// hooks/hooks.json
{
  "hooks": [
    {
      "name": "my-pre-tool",
      "event": "preToolUse",
      "script": "scripts/hooks/my-pre-tool.js",
      "description": "Validates and audits tool invocations before execution"
    }
  ]
}

```

The `"name"` field is used for debugging and logging. Multiple PreToolUse hooks can be registered; they execute in manifest order.

### Step 3: Write Tests

Validate hook behavior using the repository's test utilities. The [`tests/hooks/hooks.test.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/tests/hooks/hooks.test.js) file contains patterns for hook testing.

```javascript
// tests/hooks/hooks.test.js
import { describe, it, expect } from 'vitest';
import { runScript } from '../lib/utils.js';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

describe('PreToolUse hook: my-pre-tool', () => {
  it('blocks dangerous tools with clear error message', async () => {
    const hookPath = path.join(process.cwd(), 'scripts/hooks/my-pre-tool.js');
    const { run } = await import(hookPath);
    
    await expect(
      run({ tool: 'dangerousTool', args: ['rm -rf /'], session: {} })
    ).rejects.toThrow('blocked');
  });

  it('transforms string arguments with audit prefix', async () => {
    const { run } = await import(
      path.join(process.cwd(), 'scripts/hooks/my-pre-tool.js')
    );
    
    const result = await run({
      tool: 'read',
      args: ['sensitive/file.txt'],
      session: {}
    });

    expect(result.args[0]).toMatch(/^\[AUDITED\]/);
  });

  it('preserves non-string arguments unchanged', async () => {
    const { run } = await import(
      path.join(process.cwd(), 'scripts/hooks/my-pre-tool.js')
    );
    
    const result = await run({
      tool: 'compute',
      args: [42, { key: 'value' }, null],
      session: {}
    });

    expect(result.args[0]).toBe(42);
    expect(result.args[1]).toEqual({ key: 'value' });
    expect(result.args[2]).toBeNull();
  });
});

```

## PreToolUse Hook Use Cases

### Security and Access Control

```javascript
// scripts/hooks/security-gate.js
export async function run(context) {
  const sensitivePaths = ['/etc/', '/root/', '.env', 'secrets/'];
  const hasSensitiveArg = context.args.some(arg => 
    typeof arg === 'string' && sensitivePaths.some(p => arg.includes(p))
  );

  if (hasSensitiveArg && !context.session.authenticated) {
    throw new Error('Authentication required for sensitive path access');
  }

  return context;
}

```

### Argument Sanitization

```javascript
// scripts/hooks/sanitizer.js
export async function run(context) {
  // Prevent shell injection in string arguments
  const SHELL_CHARS = /[;&|`$]/;
  
  context.args = context.args.map(arg => {
    if (typeof arg === 'string' && SHELL_CHARS.test(arg)) {
      throw new Error(`Potentially unsafe characters in argument: "${arg}"`);
    }
    return arg;
  });

  return context;
}

```

### Rate Limiting and Quotas

```javascript
// scripts/hooks/rate-limiter.js
const toolCallCounts = new Map();

export async function run(context) {
  const sessionId = context.session.id;
  const key = `${sessionId}:${context.tool}`;
  const current = toolCallCounts.get(key) || 0;
  
  const limit = context.session.toolLimits?.[context.tool] || 100;
  
  if (current >= limit) {
    throw new Error(`Rate limit exceeded for tool: ${context.tool}`);
  }
  
  toolCallCounts.set(key, current + 1);
  return context;
}

```

## Key Reference Files in everything-claude-code

| File | Path | Description |
|------|------|-------------|
| [`pre-compact.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/pre-compact.js) | [`scripts/hooks/pre-compact.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/scripts/hooks/pre-compact.js) | Reference implementation showing session state mutation patterns |
| [`hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks.json) | [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) | Central registry defining all hook event bindings |
| [`hooks.test.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks.test.js) | [`tests/hooks/hooks.test.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/tests/hooks/hooks.test.js) | Comprehensive test suite for hook validation |
| [`hooks.md`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks.md) | [`rules/hooks.md`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/rules/hooks.md) | Documentation of supported hook events including `preToolUse` |

The [`rules/hooks.md`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/rules/hooks.md) file specifies that `preToolUse` is one of several lifecycle events, alongside `postToolUse`, `preCompact`, and `sessionStart`. Each event receives a context object with event-specific properties.

## Common Pitfalls and Solutions

- **Forgetting to return context**: Always return the context object, even if unmodified, or subsequent hooks and tool execution receive `undefined`.
- **Synchronous handlers**: Wrap synchronous logic in `Promise.resolve()` or mark `run` as `async` to ensure compatibility with the hook orchestrator.
- **Context mutation side effects**: Clone deep objects before mutation to avoid unintended cross-hook pollution: `context.session = JSON.parse(JSON.stringify(context.session))`.

## Summary

- **PreToolUse hooks** execute JavaScript before every tool invocation in Claude Code, enabling validation, transformation, and logging.
- Implementation requires: a script in `scripts/hooks/` exporting `run(context)`, registration in [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) with `"event": "preToolUse"`, and tests in `tests/hooks/`.
- The `context` object provides `tool` (name), `args` (array), and `session` (state) for inspection and mutation.
- Hooks can abort execution by throwing errors or modify behavior by returning mutated context.
- Reference implementations in `WorldFlowAI/everything-claude-code` demonstrate production patterns for security, sanitization, and rate limiting.

## Frequently Asked Questions

### What happens if multiple PreToolUse hooks are registered?

Claude Code executes PreToolUse hooks in the order they appear in [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json). Each hook receives the context returned by the previous hook, creating a pipeline. If any hook throws an error, the tool invocation aborts immediately and subsequent hooks do not run.

### Can a PreToolUse hook modify which tool actually executes?

No. The `context.tool` property is read-only within the hook contract. While you can read the tool name to apply conditional logic, you cannot redirect execution to a different tool. To achieve tool substitution, implement custom tool wrappers or use session state to influence downstream behavior.

### How do I debug a PreToolUse hook that isn't firing?

Verify three elements: (1) the JSON entry in [`hooks/hooks.json`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/hooks/hooks.json) uses the exact string `"preToolUse"` for the `event` field, (2) the `script` path is correct relative to the repository root, and (3) the script exports `run` as a named export (not default export). Check [`rules/hooks.md`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/rules/hooks.md) for event name spelling and examine [`tests/hooks/hooks.test.js`](https://github.com/WorldFlowAI/everything-claude-code/blob/main/tests/hooks/hooks.test.js) for working examples.