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