Skip to content

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.

HookFiresEnv exported
pre-worker.shBefore each workerROLE=worker FEATURE_ID PROJECT_DIR STATE_DIR
post-worker.shAfter each workerSame + RC (worker exit code)
pre-validator.shBefore each validatorROLE=validator FEATURE_ID PROJECT_DIR STATE_DIR
post-validator.shAfter each validatorSame + RC
on-escalate.shWhen orchestrator escalatesREASON PROJECT_DIR STATE_DIR
on-feature-passed.shWhen validator marks a feature passedFEATURE_ID STATUS=passed PROJECT_DIR STATE_DIR
on-feature-failing.shWhen validator marks a feature failingFEATURE_ID STATUS=failing PROJECT_DIR STATE_DIR
on-checkpoint-fired.shWhen a checkpoint is created (auto or via orchestrator)CHECKPOINT_NAME TRIGGER PROJECT_DIR STATE_DIR
on-smoke-pass.shWhen the service smoke test passesTRIGGER=onDone|onFeaturePass FEATURE_ID? PROJECT_DIR STATE_DIR
on-smoke-fail.shWhen the service smoke test failsSame as on-smoke-pass
on-competition-start.shWhen N workers spawn on the same featureFEATURE_ID LANE_COUNT PROJECT_DIR STATE_DIR
on-competition-won.shWhen a competition winner is mergedFEATURE_ID WINNER PROJECT_DIR STATE_DIR

Example: send a Slack message on escalation

Section titled “Example: send a Slack message on escalation”
.harness/hooks/on-escalate.sh
#!/usr/bin/env bash
curl -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.

.harness/hooks/on-feature-passed.sh
#!/usr/bin/env bash
cd "$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”
.harness/hooks/on-smoke-pass.sh
#!/usr/bin/env bash
[ "$TRIGGER" = "onDone" ] || exit 0
curl -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}'

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.