Hooks Guide¶
Hooks let you execute actions when session states change. Use them to get notifications, trigger CI jobs, log events, or integrate with other systems.
Hook Types¶
Webhook¶
HTTP POST to a URL:
hooks:
- name: my-webhook
type: webhook
trigger: state_change
url: "https://your-server.com/webhook"
Shell¶
Execute a shell command:
hooks:
- name: my-command
type: shell
trigger: state_change
command: 'echo "$SESSION_NAME is $STATE"'
Triggers¶
Hooks fire based on triggers:
| Trigger | Fires When |
|---|---|
state_change |
State or detail changes |
program_change |
Detected program changes |
any_change |
Any change (state or program) |
State Change Examples¶
# When a session goes idle
- name: idle-notify
trigger: state_change
to_states: [Idle]
command: 'notify-send "agentwatch" "$SESSION_NAME finished"'
# When a session hits an error
- name: error-alert
trigger: state_change
to_states: [Error]
url: "https://alerts.example.com/webhook"
Program Change Examples¶
# When Claude Code starts (from shell)
- name: claude-started
trigger: program_change
from_programs: ["bash", "zsh"]
to_programs: ["Claude Code"]
command: 'echo "Claude Code started in $SESSION_NAME"'
# When any AI tool exits
- name: ai-exited
trigger: program_change
from_programs: ["Claude Code", "Codex"]
to_programs: ["bash", "zsh"]
command: 'echo "AI tool exited in $SESSION_NAME"'
Filters¶
Narrow which events trigger your hooks:
| Filter | Description |
|---|---|
programs |
Only fire for these programs |
states |
Only fire when in these states |
from_states |
Only fire when transitioning FROM these states |
to_states |
Only fire when transitioning TO these states |
from_programs |
Only fire when changing FROM these programs |
to_programs |
Only fire when changing TO these programs |
Filter Examples¶
# Only Claude Code errors
- name: claude-errors
trigger: state_change
programs: ["Claude Code"]
to_states: [Error]
# Only when becoming blocked from working
- name: blocked-from-working
trigger: state_change
from_states: [Working]
to_states: [Blocked]
# Any state change for Claude Code or Codex
- name: ai-any-change
trigger: state_change
programs: ["Claude Code", "Codex"]
Include Formats¶
Control what data is sent with hooks:
| Format | Description |
|---|---|
status |
Lightweight: program, state, detail only (default) |
json |
Full JSON output from detection |
text |
Plain text terminal capture |
png |
PNG screenshot |
Webhook Payloads¶
For webhooks, the format determines the POST body:
Shell Hook Files¶
For shell hooks, text and png create temporary files:
- name: capture-errors
type: shell
trigger: state_change
to_states: [Error]
include: text
command: |
# $CAPTURE_FILE contains the terminal content
cp "$CAPTURE_FILE" "/var/log/agentwatch/$SESSION_NAME-error.txt"
Environment Variables¶
Shell hooks receive these environment variables:
| Variable | Description |
|---|---|
SESSION_NAME |
tmux session name |
SESSION_ID |
tmux session ID |
PROGRAM |
Current program name |
PROGRAM_VERSION |
Program version (if detected) |
STATE |
Current state |
DETAIL |
State detail |
PREVIOUS_PROGRAM |
Previous program |
PREVIOUS_STATE |
Previous state |
PREVIOUS_DETAIL |
Previous detail |
TIMESTAMP |
ISO timestamp |
DURATION_SECONDS |
Time in previous state |
CAPTURE_FILE |
Path to capture file (if include: text/png) |
AGENTWATCH_TRIGGER |
Trigger type |
Using Variables¶
- name: log-transitions
type: shell
trigger: state_change
command: |
echo "[$(date)] $SESSION_NAME: $PREVIOUS_STATE -> $STATE ($DETAIL)" \
>> ~/agentwatch-transitions.log
Webhook Options¶
Additional options for webhooks:
- name: reliable-webhook
type: webhook
trigger: state_change
url: "https://api.example.com/webhook"
timeout_seconds: 10 # Request timeout (default: 10)
retry_count: 3 # Retries on failure (default: 0)
retry_delay_seconds: 1.0 # Delay between retries
include: json
Multiple Hooks¶
You can define multiple hooks that fire on the same events:
hooks:
# Log everything locally
- name: local-log
type: shell
trigger: any_change
command: 'echo "$TIMESTAMP $SESSION_NAME $PROGRAM $STATE" >> ~/agentwatch.log'
# Webhook for errors only
- name: error-webhook
type: webhook
trigger: state_change
to_states: [Error]
url: "https://api.example.com/errors"
# Desktop notification for blocked
- name: blocked-notify
type: shell
trigger: state_change
to_states: [Blocked]
command: 'notify-send "agentwatch" "$SESSION_NAME needs input"'
Use Case Examples¶
Desktop Notifications¶
# macOS
- name: macos-notify
type: shell
trigger: state_change
to_states: [Idle, Blocked, Error]
command: |
osascript -e 'display notification "$SESSION_NAME: $STATE" with title "agentwatch"'
# Linux (libnotify)
- name: linux-notify
type: shell
trigger: state_change
to_states: [Idle, Blocked, Error]
command: 'notify-send "agentwatch" "$SESSION_NAME: $STATE ($DETAIL)"'
Slack/Discord Integration¶
- name: slack-notify
type: webhook
trigger: state_change
to_states: [Blocked, Error]
url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
include: status
CI/CD Trigger¶
- name: trigger-tests
type: webhook
trigger: state_change
programs: ["Claude Code"]
to_states: [Idle]
url: "https://ci.example.com/api/trigger"
include: json
Error Screenshot Archive¶
- name: archive-errors
type: shell
trigger: state_change
to_states: [Error]
include: png
command: |
timestamp=$(date +%Y%m%d_%H%M%S)
cp "$CAPTURE_FILE" "/var/log/agentwatch/errors/${SESSION_NAME}_${timestamp}.png"
Sound Alert¶
- name: blocked-sound
type: shell
trigger: state_change
to_states: [Blocked]
command: 'afplay /System/Library/Sounds/Glass.aiff'
Debugging Hooks¶
Dry Run Mode¶
Test hooks without executing them:
service:
debug:
enabled: true
dry_run_hooks: true # Log hooks but don't execute
log_hooks: true # Log hook execution details
View Hook Logs¶
# Check daemon logs for hook activity
grep "hook" ~/.cache/agentwatch/daemon.log
# Or use the logs command
agentwatch logs SESSION | grep hook
Test a Webhook¶
# Manually trigger a webhook to test your endpoint
curl -X POST https://your-server.com/webhook \
-H "Content-Type: application/json" \
-d '{"session_name": "test", "state": "Error", "detail": "Test error"}'
Hook Execution¶
Order¶
Hooks execute in the order defined in the config file.
Concurrency¶
Hooks run concurrently. A slow hook won't block others.
Failures¶
Failed hooks are logged but don't affect other hooks or the daemon.
Timeouts¶
- Webhooks timeout after
timeout_seconds(default: 10) - Shell commands timeout after 30 seconds
Configuration Reference¶
Full hook schema:
hooks:
- name: string # Required: unique identifier
type: webhook | shell # Required: hook type
trigger: state_change | program_change | any_change # Required
# Filters (all optional)
programs: [string] # Current program must match
states: [string] # Current state must match
from_states: [string] # Previous state must match
to_states: [string] # Current state must match
from_programs: [string] # Previous program must match
to_programs: [string] # Current program must match
# Webhook options
url: string # Required for webhook
timeout_seconds: number # Default: 10
retry_count: number # Default: 0
retry_delay_seconds: number # Default: 1.0
# Shell options
command: string # Required for shell
# Both types
include: status | json | text | png # Default: status