Skip to main content
Hooks let you run custom logic at key points during a workflow — before a stage starts, after a run completes, when a sandbox is ready, and more. Use them for validation, notifications, guardrails, and orchestration without modifying the workflow graph itself. The examples below are configuration fragments for workflow.toml. Add them to a workflow configuration using the current run configuration schema.

Hook types

Fabro supports four hook types, from simple shell commands to full agent sessions:

Command

Use script for a shell command string. This is the simplest and most common hook type.
workflow.toml
Alternatively, command accepts an array of strings:
workflow.toml
Use either script or command for a command hook.

HTTP

POST the event context as JSON to an HTTP endpoint. Useful for webhooks, external APIs, and notification services.
workflow.toml
{{ vars.NAME }} is substituted when the run is created. Use variables only for non-sensitive metadata. Do not store tokens, API keys, or other credentials in variables or literal hook configuration. {{ env.NAME }} and {{ secrets.NAME }} are not available in hooks.

Prompt

A single-turn LLM call that evaluates the event context and returns an ok/block decision. The model responds with structured JSON.
workflow.toml

Agent

A multi-turn agent session with full tool access (shell, file read/write, grep, glob). The agent can investigate the workspace before making a decision.
workflow.toml
The agent = "enabled" marker selects an agent hook; prompt supplies its instructions. Without that marker, prompt selects a single-turn prompt hook.

Lifecycle events

Each hook fires on a specific lifecycle event:

Configuration

Hooks are defined as [[run.hooks]] entries in any of these TOML config files:
  • .fabro/project.toml — project-level hooks, apply to all workflows in the project
  • workflow.toml — per-workflow hooks
  • ~/.fabro/settings.toml — global defaults for all runs
Each entry selects one transport: script or command, url, prompt, or agent = "enabled" together with prompt. Do not add a type field; the transport fields select the hook type. See Merging hook configs for how these layers combine.
workflow.toml

Blocking vs. non-blocking

Blocking hooks can affect workflow execution. Non-blocking hooks run for side effects only — their decisions are ignored. Blocking by default: run_start, stage_start, edge_selected, pre_tool_use, sandbox_ready. These events represent decision points where a hook can prevent or redirect execution. Non-blocking by default: All other events. Override with blocking = true if needed. When multiple blocking hooks match the same event, they run sequentially. If any hook returns a block decision, execution short-circuits — remaining hooks are skipped.

Hook decisions

Blocking hooks return a decision that controls what happens next: When multiple blocking hooks run, decisions are merged with this precedence: Block > Skip/Override > Proceed.

Command hook decisions

Command hooks communicate decisions via exit code and stdout: To return an explicit decision from a command hook, print JSON to stdout:

Prompt and agent hook decisions

Prompt and agent hooks return a JSON response:
If the LLM fails to produce valid JSON, the hook fails open (proceeds). This fail-open behavior also applies to timeouts and LLM errors.

Matchers

The matcher field is a regex that filters when a hook fires. Omit matcher to match all occurrences of the event. Each event type matches against different context fields: The matcher is a regex, so write_file|edit_file matches either tool and ^agent$ matches exactly the handler type agent. When an event has multiple matchable fields (e.g., tool events match against both tool_name and node_id), the hook fires if any field matches the regex. For tool events, the matcher is tested against the tool’s internal name (e.g., shell, write_file, edit_file). See Tools for the full list. To match all file-writing tools across providers, use write_file|edit_file|apply_patch.

Examples

This example auto-formats Rust code whenever the agent writes or edits a file:
workflow.toml
More examples:
workflow.toml

Execution environment

Sandbox vs. host

By default, command hooks run inside the sandbox (sandbox = true). This means they execute in the same environment as the agent’s tools — same filesystem, same installed packages. Set sandbox = false to run on the host machine. This is useful for hooks that need access to host-only resources (CI systems, local credentials, notification tools). HTTP, prompt, and agent hooks ignore this setting — HTTP calls are always made from the host, and prompt/agent hooks use the LLM API directly.

Environment variables

Command hooks receive these environment variables:

Hook context

The full event context is available as a JSON payload. For command hooks running in the sandbox, it is written to a temp file (path in FABRO_HOOK_CONTEXT). For command hooks running on the host (sandbox = false), it is piped to stdin. For HTTP hooks, it is the POST body.
Fields vary by event — edge_from/edge_to/edge_label are only set for edge_selected, failure_reason for failure events, attempt/max_attempts for stage_retrying, etc. Null fields are omitted from the serialized JSON. For tool-level events (pre_tool_use, post_tool_use, post_tool_use_failure), the context includes additional fields:

Timeouts

Each hook type has a default timeout: Override with timeout on any hook definition, using a duration string such as timeout = "30s" or timeout = "2m". Supported units are ms, s, m, h, and d. Prompt and agent hooks fail open on timeout — execution proceeds as if the hook returned ok: true.

Fail-open behavior

Hooks are designed to be safe by default. Several failure modes result in the hook proceeding rather than blocking:
  • Prompt/agent LLM call fails — proceeds
  • Prompt/agent hook times out — proceeds
  • Prompt hook returns unparseable JSON — proceeds
  • HTTP hook returns non-2xx — proceeds
  • HTTP hook connection fails — proceeds
Command hooks do not fail open. A non-zero exit code (other than 0 or 2) produces a block decision.

Merging hook configs

Hooks from multiple config files are merged in this order (later layers replace hooks with the same id):
  1. ~/.fabro/settings.toml — global defaults
  2. .fabro/project.toml — project-level overrides
  3. workflow.toml — per-workflow overrides
Hooks with matching id values replace in place. Hooks without an id append after inherited hooks, even when their name matches. Use id when a workflow should override an inherited hook. This lets you define global hooks at the server level, project-wide hooks in .fabro/project.toml, and override or extend them per workflow. See configuration precedence for the complete layer order.

Full example

workflow.toml