Hooks

This topic describes how to use hooks to run custom code at key points in the agent lifecycle. Hooks let you intercept tool calls, audit agent behavior, enforce policies, inject context, and control execution flow.

How hooks work

Hooks follow a four-step process: an event fires, the SDK checks if any matcher applies, it calls your callback, and your callback returns a decision that controls what happens next.

  1. The agent triggers an event (for example, it’s about to call a tool).
  2. The SDK checks each hook matcher registered for that event type.
  3. If a matcher matches (or has no matcher pattern, meaning it matches everything), the SDK calls the associated callback functions.
  4. Each callback returns an output object that can allow, block, or modify the operation.

Hook events

The following events are available:

EventWhen it fires
PreToolUseBefore a tool executes. Can block the tool or modify its input.
PostToolUseAfter a tool executes successfully. Can inject additional context.
PostToolUseFailureAfter a tool execution fails.
UserPromptSubmitWhen the user sends a prompt. Can inject additional context.
StopWhen the agent is about to stop. Can inject context or halt the session.
SubagentStartWhen a sub-agent starts.
SubagentStopWhen a sub-agent is about to stop.
PreCompactBefore the conversation is compacted (summarized to fit the context window).
NotificationWhen the agent emits a notification.
PermissionRequestWhen a tool permission check occurs.

Configuring hooks

Pass hooks through the hooks option when creating a session. Each hook event maps to a list of matchers, and each matcher contains a list of callback functions.

import { createCortexCodeSession } from "cortex-code-agent-sdk";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PreToolUse: [
      {
        matcher: "Bash",
        hooks: [
          async (input, toolUseId, context) => {
            console.log("Bash command:", input.tool_input);
            return {};
          },
        ],
      },
    ],
  },
});

Matchers

Each hook matcher has three fields:

FieldTypeDescription
matcherstring (optional)A regex pattern used by events that support matching. PreToolUse, PostToolUse, and PermissionRequest match on tool name. Notification matches on notification type. PreCompact matches on the trigger value ("auto" or "manual"). If omitted, the hook fires for all values for that event.
hookslist of callbacksOne or more callback functions to run when the matcher matches.
timeoutnumber (optional)Maximum time in seconds for all callbacks in this matcher. Defaults to 60.

The matcher field accepts any valid regex pattern. For example:

  • "Bash" – matches only the Bash tool
  • "Write|Edit" – matches Write or Edit
  • ".*" – matches all tools (same as omitting the matcher)

Callback inputs

Every callback receives three arguments:

ArgumentDescription
inputAn object containing event-specific data. All events include session_id, transcript_path, cwd, permission_mode, and hook_event_name. Tool events also include tool_name, tool_input, and tool_use_id.
toolUseId / tool_use_idThe tool use ID (for tool-related events) or null.
contextA context object. Reserved for future use (for example, abort signals).

Input fields vary by event:

EventAdditional input fields
PreToolUsetool_name, tool_input, tool_use_id
PostToolUsetool_name, tool_input, tool_response, tool_use_id
PostToolUseFailuretool_name, tool_input, tool_use_id, error, optional is_interrupt
UserPromptSubmitprompt
Stopstop_hook_active
SubagentStartagent_id, agent_type
SubagentStopstop_hook_active, agent_id, agent_transcript_path, agent_type
PreCompacttrigger ("manual" or "auto"), custom_instructions
Notificationmessage, notification_type, optional title
PermissionRequesttool_name, tool_input, optional permission_suggestions

Tool-lifecycle hooks and PermissionRequest can also include optional agent_id and agent_type fields when they fire from a sub-agent.

Callback outputs

Callbacks return an output object that controls execution. The following fields are available:

FieldTypeDescription
continue / continue_booleanWhether processing should proceed. Set to false to stop further processing for the current hook site or turn. Default: true.
stopReasonstringMessage shown when continue is false.
decision"block"Set to "block" to block the current operation.
reasonstringFeedback message for the agent about the decision.
systemMessagestringWarning message displayed to the user.
hookSpecificOutputobjectEvent-specific controls (see below).

Note

The Python SDK uses continue_ (with a trailing underscore) instead of continue to avoid the Python keyword conflict. The SDK automatically converts this to continue when communicating with the CLI.

Hook-specific outputs

The hookSpecificOutput field accepts event-specific controls:

PreToolUse

FieldDescription
permissionDecision"allow", "deny", or "ask". Controls whether the tool can execute.
permissionDecisionReasonReason for the permission decision.
updatedInputModified tool input to use instead of the original.

PostToolUse

FieldDescription
additionalContextExtra context injected into the conversation after tool execution.

UserPromptSubmit

FieldDescription
additionalContextExtra context injected into the conversation.

PermissionRequest

FieldDescription
decision.behavior"allow" or "deny". Controls the permission result.
decision.messageMessage shown when denying the permission request.

Examples

Block dangerous tools

Prevent the agent from running specific bash commands:

import { createCortexCodeSession } from "cortex-code-agent-sdk";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PreToolUse: [
      {
        matcher: "Bash",
        hooks: [
          async (input) => {
            const command = (input.tool_input as any)?.command ?? "";
            if (command.includes("rm -rf") || command.includes("DROP TABLE")) {
              return {
                decision: "block",
                reason: "Destructive commands are not allowed",
              };
            }
            return {};
          },
        ],
      },
    ],
  },
});

Auto-allow read-only permission requests

Allow permission requests for tools that only read data:

hooks: {
  PermissionRequest: [
    {
      matcher: "Read%Glob%Grep",
      hooks: [
        async (input) => {
          return {
            hookSpecificOutput: {
              hookEventName: "PermissionRequest",
              decision: { behavior: "allow" },
            },
          };
        },
      ],
    },
  ],
}

Modify tool input

Add a timeout to all bash commands before they execute:

hooks: {
  PreToolUse: [
    {
      matcher: "Bash",
      hooks: [
        async (input) => {
          const originalCommand = (input.tool_input as any)?.command ?? "";
          return {
            hookSpecificOutput: {
              hookEventName: "PreToolUse",
              updatedInput: { command: `timeout 30 ${originalCommand}` },
            },
          };
        },
      ],
    },
  ],
}

Audit logging

Log all tool calls for auditing purposes without affecting execution:

import { createCortexCodeSession } from "cortex-code-agent-sdk";
import { appendFileSync } from "node:fs";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PostToolUse: [
      {
        hooks: [
          async (input) => {
            const entry = {
              timestamp: new Date().toISOString(),
              tool: input.tool_name,
              input: input.tool_input,
              sessionId: input.session_id,
            };
            appendFileSync("audit.log", JSON.stringify(entry) + "\n");
            return {};
          },
        ],
      },
    ],
  },
});

Hooks vs. canUseTool

Both hooks and the canUseTool callback can intercept tool calls, but they serve different purposes:

FeaturecanUseToolHooks
ScopePre-execution permission check onlyMultiple lifecycle events (tool lifecycle, prompt submit, stop, sub-agent lifecycle, notification, and compaction)
EventsOne: permission requestTen: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStart, SubagentStop, Notification, and PreCompact
Pattern matchingNo matcher support. Invoked for permission requests that are not already resolved by rule lists.Yes (regex matchers filter by tool name, notification type, or compaction trigger depending on the event)
Modify inputLimited. updatedInput is currently used for SDK-routed pseudo-tools such as AskUserQuestion and ExitPlanMode.Yes (hookSpecificOutput.updatedInput)
Best forSimple allow/deny decisionsAudit logging, context injection, complex policies, post-execution actions

You can use both together. The canUseTool callback runs as part of the CLI’s permission system, while PreToolUse hooks run separately.

Legal notices

Where your configuration of Cortex Code uses a model provided on the Model and Service Pass-Through Terms, your use of that model is further subject to the terms for that model on that page.

The data classification of inputs and outputs are as set forth in the following table.

Input data classificationOutput data classificationDesignation
Usage DataCustomer DataCovered AI Features [1]

For additional information, refer to Snowflake AI and ML.