Lifecycle hooks
Lifecycle hooks fire at agent boundaries. Drop an executable shell script at .harness/hooks/<name>.sh and the harness will invoke it at the matching event. Output lands in .harness/logs/hooks/<ts>-<name>.log. Non-zero exits are warned but don’t halt the mission.
All 12 hooks
Section titled “All 12 hooks”| Hook | Fires | Env exported |
|---|---|---|
pre-worker.sh | Before each worker | ROLE=worker FEATURE_ID PROJECT_DIR STATE_DIR |
post-worker.sh | After each worker | Same + RC (worker exit code) |
pre-validator.sh | Before each validator | ROLE=validator FEATURE_ID PROJECT_DIR STATE_DIR |
post-validator.sh | After each validator | Same + RC |
on-escalate.sh | When orchestrator escalates | REASON PROJECT_DIR STATE_DIR |
on-feature-passed.sh | When validator marks a feature passed | FEATURE_ID STATUS=passed PROJECT_DIR STATE_DIR |
on-feature-failing.sh | When validator marks a feature failing | FEATURE_ID STATUS=failing PROJECT_DIR STATE_DIR |
on-checkpoint-fired.sh | When a checkpoint is created (auto or via orchestrator) | CHECKPOINT_NAME TRIGGER PROJECT_DIR STATE_DIR |
on-smoke-pass.sh | When the service smoke test passes | TRIGGER=onDone|onFeaturePass FEATURE_ID? PROJECT_DIR STATE_DIR |
on-smoke-fail.sh | When the service smoke test fails | Same as on-smoke-pass |
on-competition-start.sh | When N workers spawn on the same feature | FEATURE_ID LANE_COUNT PROJECT_DIR STATE_DIR |
on-competition-won.sh | When a competition winner is merged | FEATURE_ID WINNER PROJECT_DIR STATE_DIR |
Example: send a Slack message on escalation
Section titled “Example: send a Slack message on escalation”#!/usr/bin/env bashcurl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"Mission escalated: $REASON\"}" \ "$SLACK_WEBHOOK_URL"Make it executable: chmod +x .harness/hooks/on-escalate.sh.
Example: open a PR on every feature pass
Section titled “Example: open a PR on every feature pass”#!/usr/bin/env bashcd "$PROJECT_DIR"gh pr create --head "harness/$FEATURE_ID" --base main \ --title "harness: $FEATURE_ID" \ --body "Autogenerated. See .harness/issues.md for validator notes."Example: invalidate a CDN cache when smoke test passes after DONE
Section titled “Example: invalidate a CDN cache when smoke test passes after DONE”#!/usr/bin/env bash[ "$TRIGGER" = "onDone" ] || exit 0curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ --data '{"purge_everything":true}'Why hooks instead of in-prompt actions
Section titled “Why hooks instead of in-prompt actions”Hooks are out-of-band: they don’t consume tokens, they don’t add latency to the LLM call, and they fire deterministically (not “if Claude decides to”).
Anything you’d otherwise put in a role prompt as “and also send a Slack message” should be a hook. Keep prompts focused on the role’s one job.