Skip to content

Hooks Reference

Configuration, payload format, and delivery semantics for HZL lifecycle hooks.

See Lifecycle Hooks for the design rationale and integration patterns.

Configuration

Hooks are configured in config.json:

json
{
  "hooks": {
    "on_done": {
      "url": "https://your-endpoint.example.com/hooks/callback",
      "headers": {
        "Authorization": "Bearer <token>"
      },
      "body": {
        "message": "HZL task completed.",
        "agentId": "main"
      }
    }
  }
}

Config file location:

  • Production: $XDG_CONFIG_HOME/hzl/config.json (typically ~/.config/hzl/config.json)
  • Dev mode: .config/hzl/config.json (in repo root)
FieldTypeRequiredDescription
hooks.on_done.urlstringYesHTTP(S) endpoint to receive POST requests
hooks.on_done.headersobjectNoAdditional headers sent with each request
hooks.on_done.bodyobjectNoAdditional JSON fields merged into the POST body

If hooks.on_done.url is not set, no outbox rows are created and hzl hook drain is a no-op.

Supported triggers

TriggerEventStatus
on_doneTask transitions to doneSupported

Only on_done is currently supported. Other lifecycle events (creation, blocking, lease expiry, stale detection) are not hooked. Use hzl events for raw lifecycle reads, and hzl task stuck --json --stale for recovery polling.

Payload format

Each delivery is an HTTP POST with Content-Type: application/json:

json
{
  "trigger": "on_done",
  "task": {
    "task_id": "01KJNYR7...",
    "title": "Implement auth flow",
    "project": "my-project",
    "status": "done",
    "priority": 1,
    "agent": "worker-1",
    "lease_until": "2026-03-01T14:00:00.000Z"
  },
  "transition": {
    "from": "in_progress",
    "to": "done"
  },
  "timestamp": "2026-03-01T12:30:00.000Z",
  "context": {
    "author": "worker-1",
    "agent_id": "run-2026-03-01-01",
    "session_id": null,
    "correlation_id": null,
    "causation_id": null
  }
}
FieldDescription
triggerAlways "on_done" for this hook type
taskSnapshot of task state at completion time
transition.fromPrevious status (in_progress or blocked)
transition.toAlways "done"
timestampISO 8601 time when the hook was enqueued
contextEvent metadata from the completing command

Delivery

Running the drain

bash
# Process all queued hooks
hzl hook drain

# Process at most 10 hooks this run
hzl hook drain --limit 10

# JSON output for scripting
hzl hook drain --json

hzl hook drain is a one-shot command. It processes queued deliveries and exits. Schedule it externally:

bash
# cron (every 2 minutes)
*/2 * * * * hzl hook drain

# systemd timer, launchd plist, or orchestrator scheduler

Drain output

json
{
  "worker_id": "hook-drain-12345-abc...",
  "claimed": 3,
  "attempted": 3,
  "delivered": 2,
  "retried": 1,
  "failed": 0,
  "reclaimed": 0,
  "reclaimed_failed": 0,
  "preflight_failed": 0,
  "duration_ms": 450
}
FieldDescription
claimedRows claimed for processing this run
deliveredSuccessfully delivered
retriedFailed but will retry on next drain
failedPermanently failed (max attempts or TTL exceeded)
reclaimedStale processing locks recovered to queued
reclaimed_failedStale locks moved directly to failed

Retry and backoff

Failed deliveries retry with exponential backoff:

ParameterDefaultDescription
Max attempts5Total delivery attempts before permanent failure
TTL24 hoursMaximum age before a queued record expires
Backoff base30 secondsInitial retry delay
Backoff max6 hoursMaximum retry delay
Jitter20%Random variance on backoff to avoid thundering herd
Lock timeout5 minutesProcessing lock expiry (prevents stuck drains)
Request timeout10 secondsHTTP request timeout per delivery

A delivery is permanently marked failed when either max attempts or TTL is exceeded.

Concurrency safety

Multiple hzl hook drain processes can run concurrently. Each drain claims rows with an exclusive lock token. Stale locks (from a crashed drain process) are automatically reclaimed on the next run.

Debugging

Check outbox state

The outbox table lives in cache.db. Inspect it directly for stuck or failed deliveries:

bash
sqlite3 ~/.local/share/hzl/cache.db \
  "SELECT id, status, attempts, last_error, created_at FROM hook_outbox ORDER BY created_at DESC LIMIT 10"

Common issues

SymptomCauseFix
Hooks never deliverhzl hook drain not scheduledAdd to cron or orchestrator scheduler
delivered: 0, retried: NTarget endpoint returning errorsCheck endpoint availability, review last_error
reclaimed: N on every drainPrevious drain process crashingCheck for OOM or timeout issues in drain host
No outbox rows createdhooks.on_done.url not configuredSet URL in config.json

HZL - Shared task ledger for OpenClaw