> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

Hooks allow you to inject custom logic at key lifecycle points of an AI session, enabling audit logging, security controls, context injection, and dynamic behavior modification.

<div id="event-overview" />

## Event Overview

| Event                | Trigger                       | Controllable Behavior                    |
| -------------------- | ----------------------------- | ---------------------------------------- |
| `PreToolUse`         | Before tool invocation        | Intercept / allow / modify input         |
| `PostToolUse`        | After tool succeeds           | Audit / inject context / override output |
| `PostToolUseFailure` | After tool fails              | Error handling / logging                 |
| `UserPromptSubmit`   | Before user prompt is sent    | Inject context / intercept               |
| `SessionStart`       | Session begins                | Initialize / inject context              |
| `SessionEnd`         | Session ends                  | Cleanup / logging                        |
| `Stop`               | AI stops generating           | Prevent stop, force continuation         |
| `SubagentStart`      | Subagent starts               | Observe / log                            |
| `SubagentStop`       | Subagent stops                | Observe / log                            |
| `PreCompact`         | Before context compaction     | Observe / log                            |
| `PostCompact`        | After context compaction      | Observe / log                            |
| `CwdChanged`         | Working directory changes     | Observe / log                            |
| `InstructionsLoaded` | Instruction file loaded       | Observe / log                            |
| `FileChanged`        | File created/modified/deleted | Observe / log                            |
| `PermissionRequest`  | Permission requested          | Auto-approve / deny permission requests  |

For complete event type definitions, see [Hooks Reference](/en/cli/sdk/references#hooks-reference).

<div id="configuration" />

## Configuration

Configure hooks in `QueryOptions.hooks`:

```typescript theme={null}
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback, HookCallbackMatcher } from '@qoder-ai/qoder-agent-sdk';

const result = query({
  prompt: 'perform task',
  options: {
    hooks: {
      PreToolUse: [{ matcher: 'Bash', hooks: [myHook] }],
      PostToolUse: [{ hooks: [auditHook] }],
      SessionEnd: [{ hooks: [logHook] }],
    },
  },
});

for await (const message of result) {
  // process messages
}
```

<div id="matchers" />

### Matchers

The `matcher` field is a regex pattern — hooks only fire when the tool name matches:

```typescript theme={null}
const result = query({
  prompt: 'perform task',
  options: {
    hooks: {
      PreToolUse: [
        { matcher: 'Bash', hooks: [bashAuditHook] },           // Bash only
        { matcher: 'File.*|Write|Edit', hooks: [fileAuditHook] },  // File operations
        { hooks: [generalLogHook] },                            // All tools (no matcher)
      ],
    },
  },
});
```

<div id="callback-functions" />

### Callback Functions

Each hook callback receives the event input, tool use ID, and abort signal:

```typescript theme={null}
type HookCallback = (
  input: HookInput,
  toolUseID: string | undefined,
  options: { signal: AbortSignal },
) => Promise<HookJSONOutput>;
```

<div id="inputs" />

#### Inputs

All events share common fields: `hook_event_name` (event type), `session_id` (session ID), `transcript_path` (transcript file path), `cwd` (working directory). Each event also has event-specific fields, such as `tool_name` and `tool_input` for `PreToolUse`.

For complete input type definitions, see [Hooks Reference](/en/cli/sdk/references#basehookinput).

<div id="outputs" />

#### Outputs

Callbacks return an object that controls behavior through these fields:

* `continue: false` — Terminate the session
* `decision: "block"` + `reason` — Block tool execution or prevent AI from stopping
* `hookSpecificOutput` — Event-specific output, such as modifying tool input (`updatedInput`), overriding tool output (`updatedToolOutput`), or injecting context (`additionalContext`)

For complete output type definitions, see [Hooks Reference](/en/cli/sdk/references#hookjsonoutput).

<div id="examples" />

## Examples

<div id="security-interception-pretooluse" />

### Security Interception (PreToolUse)

Block dangerous shell commands:

```typescript theme={null}
const securityHook: HookCallback = async (input) => {
  if (input.hook_event_name !== 'PreToolUse') return {};

  if (input.tool_name === 'Bash') {
    const cmd = String((input.tool_input as any)?.command ?? '');
    if (cmd.includes('rm -rf')) {
      return {
        hookSpecificOutput: {
          hookEventName: 'PreToolUse',
          permissionDecision: 'deny',
          permissionDecisionReason: 'Destructive delete operations are not allowed',
        },
      };
    }
  }

  return {};
};
```

<div id="redact-sensitive-information-posttooluse" />

### Redact Sensitive Information (PostToolUse)

Override tool output to replace AK/Token and other sensitive information:

```typescript theme={null}
const secretRedactHook: HookCallback = async (input) => {
  if (input.hook_event_name !== 'PostToolUse') return {};

  const content = typeof input.tool_response === 'string'
    ? input.tool_response
    : JSON.stringify(input.tool_response);

  const redacted = content
    .replace(/(?:LTAI|AKID)[A-Za-z0-9]{16,}/g, '<REDACTED_AK>')
    .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, 'Bearer <REDACTED>');

  if (redacted === content) return {};

  return {
    hookSpecificOutput: {
      hookEventName: 'PostToolUse',
      updatedToolOutput: redacted,
    },
  };
};
```

<div id="truncate-long-output-posttooluse" />

### Truncate Long Output (PostToolUse)

Trim overly long Bash output, keeping head and tail:

```typescript theme={null}
const bashSummarizeHook: HookCallback = async (input) => {
  if (input.hook_event_name !== 'PostToolUse') return {};
  if (input.tool_name !== 'Bash') return {};

  const content = String(input.tool_response ?? '');
  const THRESHOLD = 50 * 1024;
  if (content.length <= THRESHOLD) return {};

  const head = content.slice(0, 8 * 1024);
  const tail = content.slice(-4 * 1024);
  const omitted = content.length - head.length - tail.length;
  return {
    hookSpecificOutput: {
      hookEventName: 'PostToolUse',
      updatedToolOutput: `${head}\n\n[... OMITTED ${omitted} chars ...]\n\n${tail}`,
    },
  };
};
```

<div id="force-continuation-stop" />

### Force Continuation (Stop)

Prevent the AI from stopping when the task is incomplete:

```typescript theme={null}
const keepGoingHook: HookCallback = async (input) => {
  if (input.hook_event_name !== 'Stop') return {};

  if (!isTaskComplete()) {
    return {
      decision: 'block',
      reason: 'Please continue completing the remaining tasks',
    };
  }
  return {};
};
```

<div id="auto-approve-permissions-permissionrequest" />

### Auto-Approve Permissions (PermissionRequest)

Automatically approve Read tool permission requests:

```typescript theme={null}
const autoApproveRead: HookCallback = async (input) => {
  if (input.hook_event_name !== 'PermissionRequest') return {};

  if (input.tool_name === 'Read') {
    return {
      hookSpecificOutput: {
        hookEventName: 'PermissionRequest',
        decision: { behavior: 'allow' },
      },
    };
  }
  return {};
};
```

> For the complete permission model, see [Permissions](/en/cli/sdk/permissions).

<div id="audit-and-security-controls-combined" />

### Audit and Security Controls (Combined)

Combine audit logging with security interception:

```typescript theme={null}
import { query } from '@qoder-ai/qoder-agent-sdk';
import type { HookCallback } from '@qoder-ai/qoder-agent-sdk';
import * as fs from 'fs';

const auditLog = fs.createWriteStream('audit.log', { flags: 'a' });

const securityHook: HookCallback = async (input, toolUseID) => {
  if (input.hook_event_name === 'PreToolUse') {
    // Audit log
    auditLog.write(JSON.stringify({
      event: 'tool_call',
      tool: input.tool_name,
      input: input.tool_input,
      timestamp: new Date().toISOString(),
    }) + '\n');

    // Security check: block curl to external domains
    if (input.tool_name === 'Bash') {
      const cmd = String((input.tool_input as any)?.command ?? '');
      if (/curl\s+https?:\/\/(?!localhost)/.test(cmd)) {
        return {
          hookSpecificOutput: {
            hookEventName: 'PreToolUse',
            permissionDecision: 'deny',
            permissionDecisionReason: 'HTTP requests to external domains are not allowed',
          },
        };
      }
    }
  }

  return {};
};

const result = query({
  prompt: 'run deployment',
  options: {
    hooks: {
      PreToolUse: [{ hooks: [securityHook] }],
    },
  },
});

for await (const message of result) {
  // process messages
}
```

***

<div id="notes" />

## Notes

* Hook callbacks should return quickly to avoid blocking AI execution.
* `matcher` uses JavaScript regex syntax, matching the `tool_name` field.
* `continue: false` terminates the session — only effective for `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `UserPromptSubmit`, `Stop`, `SubagentStop` events. Observation events (e.g. `SessionEnd`, `CwdChanged`) ignore this field.
* When multiple hooks return conflicting `decision` values, `"deny"` / `"block"` takes precedence (strictest rule wins).
* When multiple hooks set `updatedToolOutput`, the **last non-empty value** wins. For chained transforms (e.g. redact then truncate), execute them sequentially within a single callback.
