Jaiph Hooks

When you run a workflow with jaiph run, the CLI observes a fixed lifecycle: the run is prepared, the workflow process starts, each step starts and ends as the runtime reports them, then the run finishes. Hooks let you register shell commands that run when those lifecycle points occur.

Typical uses: notifying another system when a run finishes, logging step names or timing, or appending structured data (status, run directory, summary path) somewhere you control.

Configuration comes from global and project-local hooks.json files. For each event name, if the project file lists at least one non-empty command, those commands are used and global commands for that event are ignored (there is no merging of lists). If neither file exists, or both omit an event, nothing runs for that event.

Hooks are implemented only for jaiph run. They are not used by jaiph test, jaiph report, or other commands.

Config locations

The workspace root is the first directory that contains .jaiph or .git when walking up from the workflow file’s directory. If neither marker appears on the path to the filesystem root, the workspace is the workflow file’s directory. Project-local hooks.json is always read from that workspace root.

Both files are optional.

Schema

Each file is a single JSON object. Keys must be supported event names (see below). Each value must be a JSON array; anything else for a known key is ignored for that key only. Array elements must be non-empty strings (each string is one shell command); other element types and empty strings are skipped. Unknown top-level keys are ignored. Commands for an event are spawned in array order; the next command starts without waiting for the previous one to finish (see Behavior).

{
  "workflow_start": ["echo 'run started'"],
  "workflow_end": ["curl -s -X POST https://example.com/jaiph/end -d @-"],
  "step_start": [],
  "step_end": ["jq -c . >> \"$HOME/.jaiph/step-events.jsonl\""]
}

An empty array (or omitting the key) means “no commands from this file for this event,” so resolution falls back to global hooks when the project file does not override that event (see Precedence).

Supported events

Event When it runs
workflow_start After transpile/build succeeded and before the CLI spawns the workflow subprocess (local or Docker). If build fails, this event does not run.
workflow_end After the workflow subprocess has exited (any status). Runs before the CLI prints its final PASS/FAIL line.
step_start When the runtime reports that a step (workflow, rule, function, or prompt) has started.
step_end When the runtime reports that a step has finished.

Step hooks mirror the same stderr __JAIPH_EVENT__ stream the CLI uses for the progress tree; see CLI — Run progress and tree output.

Precedence

For each event, project-local commands override global commands:

There is no way to explicitly disable a global hook from a project file alone. Omitting an event or using [] means “fall back to global.” To suppress a global hook for one project, override that event with a no-op (e.g. "workflow_end": ["true"]).

Payload

Each command receives one JSON object on stdin (UTF-8). Use jq, python3 -c, or any parser you like.

Payload fields:

Field Present Description
event Always workflow_start, workflow_end, step_start, or step_end.
workflow_id Always Runtime run id from stderr events. Empty string on workflow_start; for step events, whatever the runtime sent (may be empty until the runtime assigns an id).
step_id step_* Step identifier (id from the runtime event).
step_kind step_* workflow, rule, function, or prompt.
step_name step_* Step name (e.g. default, scan_passes).
status workflow_end, step_end Exit status: 0 success, non-zero failure. For workflow_end, this is the CLI’s resolved outcome: non-zero if the subprocess exited non-zero or the CLI detected fatal runtime output on stderr (see jaiph run behavior in CLI).
timestamp Always ISO 8601 time (from the CLI or runtime event).
elapsed_ms workflow_end, step_end Elapsed milliseconds: total wall time for the run (workflow_end), or step duration (step_end).
run_path Always Absolute path to the .jh / .jph file being run.
workspace Always Workspace root directory (same rules as Config locations).
run_dir workflow_end Absolute path to the run’s log directory, when the runtime wrote it into the run metadata file.
summary_file workflow_end Absolute path to run_summary.jsonl when the runtime recorded it. Append-only JSONL of workflow/step/log/inbox events; see CLI — Run summary.
out_file step_end Step stdout log path, when the runtime kept a non-empty file. Omitted if output was empty (file removed).
err_file step_end Step stderr log path, when the runtime kept a non-empty file. Omitted if stderr was empty (file removed).

Example payload (step_end):

{
  "event": "step_end",
  "workflow_id": "abc-123",
  "step_id": "run:1:1",
  "step_kind": "workflow",
  "step_name": "default",
  "status": 0,
  "timestamp": "2025-03-11T12:00:00.000Z",
  "elapsed_ms": 1500,
  "run_path": "/repo/flows/ci.jh",
  "workspace": "/repo",
  "out_file": "/repo/.jaiph/runs/.../step.out",
  "err_file": "/repo/.jaiph/runs/.../step.err"
}

Behavior

Examples

Global ~/.jaiph/hooks.json — POST the workflow-end payload to an HTTP endpoint:

{
  "workflow_end": ["curl -s -X POST https://example.com/jaiph/end -d @-"]
}

Project .jaiph/hooks.json — append a one-line JSON record per finished step, and log each workflow end under the workspace:

{
  "step_end": ["jq -c '{event,step_kind,step_name,status,elapsed_ms}' >> \"$HOME/.jaiph/step-events.jsonl\""],
  "workflow_end": ["p=$(cat); echo \"$p\" | jq -c '{event,status,run_dir,summary_file}' >> \"$(echo \"$p\" | jq -r .workspace)/.jaiph/workflow-ends.jsonl\""]
}

Stdin can only be read once per process. The workflow_end command stores the payload in p, then pipes it to each jq that needs the JSON. The step_end example uses a single jq that reads stdin once.

step_end uses a fixed path under $HOME in this example so it does not depend on where you ran jaiph run. The workflow_end line writes next to the project using the workspace field from the payload.

Project overrides global: If global has workflow_end: ["global-notify.sh"] and project has workflow_end: ["project-notify.sh"], only project-notify.sh runs.