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 want What 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.
Event Trigger Match Value LabelAddLabel added to session Label ID (e.g., urgent) LabelRemoveLabel removed from session Label ID LabelConfigChangeLabel configuration changed Always matches PermissionModeChangePermission mode changed New mode name FlagChangeSession flagged/unflagged true or falseTodoStateChangeTodo status changed New status (e.g., done) SchedulerTickRuns every minute Uses 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).
Event Trigger Match Value PreToolUseBefore a tool executes Tool name PostToolUseAfter a tool succeeds Tool name PostToolUseFailureAfter a tool fails Tool name UserPromptSubmitUser submits a prompt Event data JSON SessionStartSession starts Event data JSON SessionEndSession ends Event data JSON StopAgent stops Event data JSON SubagentStartSubagent spawned Event data JSON SubagentStopSubagent completes Event data JSON NotificationNotification received Event data JSON PreCompactBefore context compaction Event data JSON PermissionRequestPermission requested Event 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
}
Property Type Default Description type"command"Required Hook type commandstring Required Shell command to execute timeoutnumber 60000Timeout 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"
}
Property Type Default Description type"prompt"Required Hook type promptstring Required Prompt 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:
Pattern Matches ^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
Field Range Examples Minute 0–59 0, */15Hour 0–23 9, 14Day of month 1–31 1, 15Month 1–12 1, */3Day of week 0–6 (0 = Sunday) 1-5, 0,6
Common patterns:
Cron Schedule */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:
Property Type Default Description matcherstring Match all Regex pattern for event filtering cronstring — Cron expression (SchedulerTick only) timezonestring System TZ IANA timezone for cron permissionModestring "safe"Security mode for commands labelsstring[] []Labels applied to prompt-created sessions enabledboolean trueSet to false to disable without deleting hooksarray Required Array 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:
Variable Description 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_FLAGGED — true 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:
Mode Behaviour Use Case safeCommands checked against allowlist Default, recommended allow-allBypass security checks Trusted 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:
Event Max fires / minute SchedulerTick60 (1/sec) All other events 10
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
Troubleshooting: Hook not firing
Check event name — Must be exact (e.g., LabelAdd not labeladd)
Check matcher — Regex must match the event’s match value
Check cron — For SchedulerTick, verify your cron expression
Check enabled — Ensure the matcher doesn’t have "enabled": false
Check logs — Look for [hooks] in the application logs
Troubleshooting: Command blocked
If you see “Bash command blocked” errors:
Add "permissionMode": "allow-all" to the matcher
Or simplify the command to avoid shell constructs like $()
Troubleshooting: Prompt not creating session
Ensure the event is an App event (prompt hooks don’t work with Agent events)
Check that the prompt text is not empty
Verify @mentions reference valid sources or skills