Skip to main content
Just ask your agent. Say things like “create a hook that notifies me when a session is flagged” or “set up a daily morning briefing at 9am”. The agent will create and validate the configuration for you.
Hooks let you trigger actions automatically when events happen in Craft Agent. You can run shell commands, send prompts to start new sessions, or schedule recurring automations — all configured in a single JSON file.

Getting Started

The easiest way to set up hooks is to describe what you want in plain language. Here are some prompts you can try:
What you wantWhat to say
Scheduled briefing”Set up a daily standup briefing every weekday at 9am”
Desktop notifications”Notify me with a macOS notification when a session is labelled urgent”
Audit logging”Log all permission mode changes to a file”
Auto-label”When a session starts, run a command that checks the working directory and logs it”
Recurring report”Every Friday at 5pm, create a session that summarises this week’s completed tasks”
Webhook integration”When I flag a session, send a curl request to my webhook URL”

Your First Hook

Here’s the simplest possible hook — it logs a message every time a label is added to a session:
{
  "version": 1,
  "hooks": {
    "LabelAdd": [
      {
        "hooks": [
          { "type": "command", "command": "echo \"Label added: $CRAFT_LABEL\" >> ~/craft-hooks.log" }
        ]
      }
    ]
  }
}
Save this as ~/.craft-agent/workspaces/{workspaceId}/hooks.json and it starts working immediately — no restart needed.

Scheduling a Prompt

Want Craft Agent to do something for you on a schedule? Use a prompt hook with a cron expression:
{
  "version": 1,
  "hooks": {
    "SchedulerTick": [
      {
        "cron": "0 9 * * 1-5",
        "timezone": "America/New_York",
        "labels": ["Scheduled"],
        "hooks": [
          { "type": "prompt", "prompt": "Check @github for any new issues assigned to me and summarise them" }
        ]
      }
    ]
  }
}
This creates a new session every weekday at 9 AM, activates the GitHub source, and asks the agent to check your issues. The session is automatically labelled “Scheduled” for easy filtering.

How Hooks Work

When an event fires (e.g., a label is added, a tool runs, or a cron schedule matches), Craft Agent checks your hooks configuration for matching entries and executes them. There are two types of hooks:
  • Command hooks — execute shell commands with event data available as environment variables
  • Prompt hooks — send a prompt to Craft Agent, creating a new session (App events only)

Configuration File

Hooks are configured per-workspace in hooks.json:
~/.craft-agent/workspaces/{workspaceId}/hooks.json

Basic Structure

{
  "version": 1,
  "hooks": {
    "EventName": [
      {
        "matcher": "regex-pattern",
        "hooks": [
          { "type": "command", "command": "echo 'Hello'" }
        ]
      }
    ]
  }
}
Each event type maps to an array of matchers, and each matcher contains an array of hooks to execute when the matcher’s pattern matches the event value.

Events

App Events

Triggered by Craft Agent itself. Support both command and prompt hooks.
EventTriggerMatch Value
LabelAddLabel added to sessionLabel ID (e.g., urgent)
LabelRemoveLabel removed from sessionLabel ID
LabelConfigChangeLabel configuration changedAlways matches
PermissionModeChangePermission mode changedNew mode name
FlagChangeSession flagged/unflaggedtrue or false
TodoStateChangeTodo status changedNew status (e.g., done)
SchedulerTickRuns every minuteUses cron matching
Status changes: To react to status/workflow state changes (e.g., when a session moves to “done” or “in-progress”), use the TodoStateChange event. The match value is the new status name, so you can use a matcher like "^done$" to trigger only on specific transitions.

Agent Events

Passed to the Claude SDK. Only command hooks are supported (no prompt hooks).
EventTriggerMatch Value
PreToolUseBefore a tool executesTool name
PostToolUseAfter a tool succeedsTool name
PostToolUseFailureAfter a tool failsTool name
UserPromptSubmitUser submits a promptEvent data JSON
SessionStartSession startsEvent data JSON
SessionEndSession endsEvent data JSON
StopAgent stopsEvent data JSON
SubagentStartSubagent spawnedEvent data JSON
SubagentStopSubagent completesEvent data JSON
NotificationNotification receivedEvent data JSON
PreCompactBefore context compactionEvent data JSON
PermissionRequestPermission requestedEvent data JSON

Hook Types

Command Hooks

Execute a shell command when the event fires. Event data is available through environment variables.
{
  "type": "command",
  "command": "echo \"Label $CRAFT_LABEL was added\" >> ~/hook-log.txt",
  "timeout": 60000
}
PropertyTypeDefaultDescription
type"command"RequiredHook type
commandstringRequiredShell command to execute
timeoutnumber60000Timeout in milliseconds

Prompt Hooks

Send a prompt to Craft Agent, creating a new session. Only works with App events.
{
  "type": "prompt",
  "prompt": "Run the @weather skill and summarize today's forecast"
}
PropertyTypeDefaultDescription
type"prompt"RequiredHook type
promptstringRequiredPrompt text to send
Features:
  • Use @mentions to reference sources or skills (e.g., @github, @linear)
  • Environment variables are expanded (e.g., $CRAFT_LABEL, ${CRAFT_SESSION_NAME})
  • Mentioned sources are automatically activated for the new session

Matchers

Regex Matching

Most events use regex matching against the event’s match value:
{
  "matcher": "^urgent$",
  "hooks": [
    { "type": "command", "command": "notify-send 'Urgent!'" }
  ]
}
Omit the matcher field to match all events of that type. Examples:
PatternMatches
^urgent$Exact match for “urgent”
bug|featureEither “bug” or “feature”
^prod-Any value starting with “prod-”
(omitted)All events of that type

Cron Matching

For SchedulerTick events, use cron expressions instead of regex:
{
  "cron": "0 9 * * 1-5",
  "timezone": "Europe/Budapest",
  "hooks": [
    { "type": "prompt", "prompt": "Give me a morning briefing" }
  ]
}
Cron format: minute hour day-of-month month day-of-week
FieldRangeExamples
Minute0–590, */15
Hour0–239, 14
Day of month1–311, 15
Month1–121, */3
Day of week0–6 (0 = Sunday)1-5, 0,6
Common patterns:
CronSchedule
*/15 * * * *Every 15 minutes
0 9 * * *Daily at 9:00 AM
0 9 * * 1-5Weekdays at 9:00 AM
30 14 1 * *1st of each month at 2:30 PM
0 */6 * * *Every 6 hours
Timezone: Uses IANA timezone names (e.g., Europe/Budapest, America/New_York). Defaults to system timezone if not specified. Verify with crontab.guru.

Matcher Options

Each matcher entry supports these optional fields:
PropertyTypeDefaultDescription
matcherstringMatch allRegex pattern for event filtering
cronstringCron expression (SchedulerTick only)
timezonestringSystem TZIANA timezone for cron
permissionModestring"safe"Security mode for commands
labelsstring[][]Labels applied to prompt-created sessions
enabledbooleantrueSet to false to disable without deleting
hooksarrayRequiredArray of command/prompt hooks
Disabling a hook: Set "enabled": false on any matcher to temporarily disable it without removing the configuration. This is useful for debugging or pausing automations.
{
  "matcher": "^urgent$",
  "enabled": false,
  "hooks": [
    { "type": "command", "command": "notify-send 'Urgent!'" }
  ]
}

Environment Variables

Common Variables

Available to all command hooks:
VariableDescription
CRAFT_EVENTEvent name (e.g., LabelAdd, PreToolUse)
CRAFT_EVENT_DATAFull event payload as JSON
CRAFT_SESSION_IDCurrent session ID
CRAFT_SESSION_NAMECurrent session name
CRAFT_WORKSPACE_IDCurrent workspace ID

App Event Variables

Dynamically generated from event payloads: Label events (LabelAdd, LabelRemove):
  • CRAFT_LABEL — Label ID being added/removed
PermissionModeChange:
  • CRAFT_OLD_MODE — Previous permission mode
  • CRAFT_NEW_MODE — New permission mode
FlagChange:
  • CRAFT_IS_FLAGGEDtrue or false
TodoStateChange:
  • CRAFT_OLD_STATE — Previous todo state
  • CRAFT_NEW_STATE — New todo state
SchedulerTick:
  • CRAFT_LOCAL_TIME — Current time (HH:MM)
  • CRAFT_LOCAL_DATE — Current date (YYYY-MM-DD)

Agent Event Variables

Tool events (PreToolUse, PostToolUse, PostToolUseFailure):
  • CRAFT_TOOL_NAME — Tool being used
  • CRAFT_TOOL_INPUT — Tool parameters as JSON
  • CRAFT_TOOL_RESPONSE — Tool output (PostToolUse only)
  • CRAFT_ERROR — Error message (PostToolUseFailure only)
Session events (SessionStart):
  • CRAFT_SOURCE — Session source (e.g., startup, resume)
  • CRAFT_MODEL — Model name
Subagent events (SubagentStart, SubagentStop):
  • CRAFT_AGENT_ID — Subagent ID
  • CRAFT_AGENT_TYPE — Subagent type

Permission Modes

Command hooks run with security checks by default. You can adjust this per matcher:
ModeBehaviourUse Case
safeCommands checked against allowlistDefault, recommended
allow-allBypass security checksTrusted automation only
Only use allow-all for commands you fully trust. It allows arbitrary shell execution.
{
  "matcher": "^urgent$",
  "permissionMode": "allow-all",
  "hooks": [
    { "type": "command", "command": "osascript -e 'display notification \"Urgent!\" with title \"Craft Agent\"'" }
  ]
}

Labels for Prompt Hooks

Prompt hooks can attach labels to the sessions they create. This makes it easy to filter and organise scheduled sessions:
{
  "cron": "0 9 * * *",
  "labels": ["Scheduled", "morning-briefing"],
  "hooks": [
    { "type": "prompt", "prompt": "Give me today's priorities" }
  ]
}
Labels also support environment variable expansion: "priority::${CRAFT_LABEL}".

Rate Limits

To prevent runaway loops (e.g., a hook that indirectly triggers itself), the event bus enforces rate limits:
EventMax fires / minute
SchedulerTick60 (1/sec)
All other events10
Excess events are silently dropped for the remainder of the 60-second window.

Examples

Daily Morning Briefing

Schedule a prompt every weekday at 9 AM:
{
  "version": 1,
  "hooks": {
    "SchedulerTick": [
      {
        "cron": "0 9 * * 1-5",
        "timezone": "Europe/Budapest",
        "labels": ["Scheduled", "briefing"],
        "hooks": [
          { "type": "prompt", "prompt": "Run the @daily-standup skill" }
        ]
      }
    ]
  }
}

Log Label Changes

Track when labels are added or removed:
{
  "version": 1,
  "hooks": {
    "LabelAdd": [
      {
        "permissionMode": "allow-all",
        "hooks": [
          { "type": "command", "command": "echo \"[$(date)] Added: $CRAFT_LABEL\" >> ~/label-log.txt" }
        ]
      }
    ],
    "LabelRemove": [
      {
        "permissionMode": "allow-all",
        "hooks": [
          { "type": "command", "command": "echo \"[$(date)] Removed: $CRAFT_LABEL\" >> ~/label-log.txt" }
        ]
      }
    ]
  }
}

macOS Notification on Urgent Label

{
  "version": 1,
  "hooks": {
    "LabelAdd": [
      {
        "matcher": "^urgent$",
        "permissionMode": "allow-all",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Urgent session flagged\" with title \"Craft Agent\"'"
          }
        ]
      }
    ]
  }
}

Audit Permission Mode Changes

{
  "version": 1,
  "hooks": {
    "PermissionModeChange": [
      {
        "permissionMode": "allow-all",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): $CRAFT_OLD_MODE -> $CRAFT_NEW_MODE\" >> ~/mode-audit.log"
          }
        ]
      }
    ]
  }
}

Validation

Ask Craft Agent to validate your hooks:
Validate my hooks configuration
Or use the config_validate tool with target: "all". The validator checks for:
  • Invalid JSON syntax
  • Unknown event names
  • Empty hooks arrays
  • Invalid cron expressions or timezones
  • Invalid or unsafe regex patterns (ReDoS prevention)
  • References to non-existent labels

Security

Hooks include several built-in safety measures:
  • Shell injection prevention — User-controlled values in environment variables (session names, labels, prompts) are automatically escaped
  • ReDoS protection — Regex patterns are limited to 500 characters and checked for catastrophic backtracking patterns
  • Rate limiting — Prevents infinite loops from hooks that trigger other hooks
  • Permission modes — Commands are checked against an allowlist by default
  • Timeouts — Commands are killed after their timeout expires (with SIGKILL fallback)
  • Start simple — Test with echo commands before writing complex scripts
  • Use labels — Tag scheduled sessions for easy filtering
  • Set timeouts — Prevent runaway commands with the timeout field
  • Log failures — Redirect stderr to track issues: command 2>> ~/hook-errors.log
  • Be specific — Use matchers to avoid triggering on every event
  • Test cron — Use crontab.guru to verify expressions
  1. Check event name — Must be exact (e.g., LabelAdd not labeladd)
  2. Check matcher — Regex must match the event’s match value
  3. Check cron — For SchedulerTick, verify your cron expression
  4. Check enabled — Ensure the matcher doesn’t have "enabled": false
  5. Check logs — Look for [hooks] in the application logs
If you see “Bash command blocked” errors:
  1. Add "permissionMode": "allow-all" to the matcher
  2. Or simplify the command to avoid shell constructs like $()
  1. Ensure the event is an App event (prompt hooks don’t work with Agent events)
  2. Check that the prompt text is not empty
  3. Verify @mentions reference valid sources or skills