You are reading Nightly documentation for 0.12.4.dev0+g50bde75.

This documentation may describe behavior that differs from Stable.

Open Stable documentation

Documentation version

0.12.4.dev0+g50bde75 · Nightly

Lifecycle hooks and policy automation

Lifecycle hooks and policy automation

Lifecycle hooks run small, declarative automations at stable boundaries in an agent session. Use
them to deny unsafe actions, request an approval, format files, run tests, notify another system,
or preserve an audit record. Hooks add policy; they never replace the normal tool, path,
information-flow, governance, or approval checks.

Manage hooks in Settings → Lifecycle Hooks or with holaryn hook. Settings exposes the
effective registry, validation errors, project trust, a dry-run order preview, and redacted
execution traces. Changes, disables, and removals take effect on the next boundary without
restarting the host.

Lifecycle boundaries

Schema version 1 defines these exact hook points:

Point Mode Mutable field
session.start, session.end Observational None
prompt.before Blocking or observational text
prompt.after Observational None
model.before Blocking or observational metadata
model.after Observational None
tool.before Blocking or observational args
tool.after, tool.error Observational None
approval.request Blocking or observational None
approval.decision Observational None
file.before_write Blocking or observational args
file.after_write Observational None
command.before Blocking or observational args
command.after Observational None
checkpoint.created, checkpoint.restored Observational None
integration.before Blocking or observational strategy
integration.after Observational None

A normal tool call reaches tool.before and then its more specific file or command boundary.
Direct slash commands, @codex/@claude-code, MCP App calls, and calls made through
run_tool_script follow the same boundaries. A denial is emitted as a visible error before the
tool runs. Worktree integration invokes integration.before only after the candidate, exact plan,
validation evidence, target revision, and explicit integration approval pass their normal checks.

Blocking hooks run serially. They may return:

  • allow to continue;
  • deny with a reason;
  • modify with only the field allowed in the table above; or
  • request_approval to enter the ordinary approval flow.

Every mutation is schema checked and the resulting action is evaluated again by the normal
approval, path, tool, and governance policies. A hook cannot rename the selected tool, manufacture
an approval decision, widen a filesystem root, or grant itself credentials. Invalid mutations fail
according to the hook's configured failure policy and are traced.

Observational hooks cannot modify or deny. They are scheduled asynchronously so a slow formatter,
test, notification, or webhook does not park unrelated sessions. Session shutdown performs a
bounded drain of outstanding observers.

Scope, override, and order

Hooks merge from least to most specific:

  1. global
  2. profile
  3. project
  4. plugin
  5. session

When a more-specific scope uses the same hook id, it replaces the less-specific definition.
After overrides, matching hooks sort by ascending order, then scope precedence, then id.
Filters are conjunctive across families and support tool-name globs, exact consequence categories,
path globs, session globs, and exact data values. The Settings registry and
holaryn hook dry-run show the resulting order.

Global and profile definitions live in the Holaryn state directory. Project definitions live at
.holaryn/hooks.json. Plugin hooks come from a plugin's hooks.json; session hooks are ephemeral.

Project trust

Opening a repository does not execute its hooks. A project hook file must validate and then be
trusted explicitly. Trust binds the canonical workspace path, hook-file path, and the exact
SHA-256 digest. Editing the file invalidates trust immediately.

holaryn hook validate .holaryn/hooks.json
holaryn hook trust-status .
holaryn hook trust .
holaryn hook trust-status .

Use holaryn hook revoke-trust . to disable the executable trust decision. In Settings, select the
workspace, review the displayed path and digest, then use Trust exact digest. The UI reports
when a file changed after review.

Project and plugin process handlers run only when the configured execution backend provides
workspace isolation. A local, non-isolating backend refuses them. This prevents a repository from
using a hook as ambient host code.

Handler types and payloads

A hook handler is one of:

  • builtin: reviewed handlers shipped with Holaryn;
  • command: an argv array with no shell parsing;
  • script: a workspace-contained script path;
  • webhook: an HTTPS URL, or loopback HTTP for local development.

Process handlers receive one JSON event on standard input and must write one JSON decision to
standard output. They receive a minimal sanitized environment plus explicitly declared,
non-secret environment values. Secret-like environment names are rejected. Output, time,
concurrency, and process-tree lifetime are bounded. After three consecutive failures, the
per-session circuit opens. The recursion depth is limited to three.

The input contract is:

{
  "schema_version": 1,
  "event_id": "hookevt_...",
  "timestamp": 0,
  "point": "tool.before",
  "session_id": "sess_...",
  "run_id": "run_...",
  "trace_id": "trace_...",
  "workspace": "/reviewed/workspace",
  "depth": 0,
  "data": {
    "call_id": "call_...",
    "tool_name": "write_file",
    "args": {"path": "notes.md", "content": "hello"},
    "category": "fs.write",
    "reversible": false
  }
}

A blocking handler can respond:

{
  "schema_version": 1,
  "action": "modify",
  "reason": "normalize the destination",
  "mutations": {
    "args": {"path": "docs/notes.md", "content": "hello"}
  },
  "output": {"rule": "documentation-path"}
}

Webhook redirects are disabled. Credential-bearing headers are rejected. Resolution blocks
link-local and private destinations except explicit loopback development URLs. Handler output and
errors are secret-redacted before persistence or event emission.

Definition example

This global blocking hook denies common credential and VCS-internal file paths:

{
  "schema_version": 1,
  "id": "protect-sensitive-paths",
  "name": "Protect sensitive paths",
  "description": "Deny writes to credential and VCS-internal paths.",
  "point": "file.before_write",
  "scope": "global",
  "scope_key": null,
  "enabled": true,
  "mode": "blocking",
  "order": 10,
  "timeout_seconds": 10,
  "max_output_bytes": 65536,
  "max_concurrency": 1,
  "failure_policy": "deny",
  "filters": {},
  "handler": {
    "kind": "builtin",
    "builtin": "forbidden-path"
  }
}

Print complete copyable examples for forbidden paths, proposed-secret approval, a formatter,
test-on-completion, and notifications:

holaryn hook examples

Save one printed hook as hook.json, then:

holaryn hook validate hook.json
holaryn hook add hook.json
holaryn hook list
holaryn hook dry-run file.before_write --data-json "{\"tool_name\":\"write_file\",\"category\":\"fs.write\",\"args\":{\"path\":\".env\"}}"
holaryn hook disable protect-sensitive-paths
holaryn hook enable protect-sensitive-paths
holaryn hook remove protect-sensitive-paths

Add --profile PROFILE_ID to manage a profile definition. Use --workspace PATH with list or
dry-run to include trusted project hooks. install-project WORKSPACE FILE writes a normalized
project document but deliberately does not trust it.

Failure behavior and diagnostics

failure_policy is one of:

  • continue: record the failure and continue;
  • warn: continue and surface the diagnostic;
  • deny: fail closed at a blocking boundary;
  • disable: disable the failing hook for the rest of that service lifetime.

Observational hooks cannot use deny. Removing or disabling a hook is safe while other executions
are in flight: current bounded work finishes, while the next registry snapshot excludes it.

Every attempted handler execution creates a versioned, redacted trace with event and execution
IDs, point, scope, duration, decision, exit status, timeout/truncation flags, and safe error text.
Inspect it in Settings or with:

holaryn hook traces --limit 50
holaryn hook traces --hook-id protect-sensitive-paths
holaryn hook traces --point file.before_write

Registry changes also emit diagnostics with the previous and current content-addressed versions.
Hook traces do not contain inherited credentials and are bounded by the local trace store.