guard.sh & verify.sh Hook Contract
eva intermediate 4 min read
ELI5
Two optional bouncers stand at the prompt’s door. The pre-send bouncer (guard.sh) frisks the inputs and either lets you in or refuses entry; the post-send bouncer (verify.sh) checks the receipt on your way out and stamps it pass or fail. Both stamps go straight into the prompt’s logbook.
Technical Deep Dive
guard.sh — pre-send
Executed only if the file exists AND is executable (bin/kick:170). Sees every var as EVA_VAR_<key>, plus EVA_PROMPT_ID and EVA_CASE. Two effects:
- Non-zero exit →
kickaborts withdie "guard.sh failed for $PROMPT_ID (exit $rc)"(bin/kick:175). No.usage.jsonlrow is appended. - Stdout
KEY=VALUElines are parsed and appended toVARS, also re-exported asEVA_VAR_<key>(bin/kick:176-181). This is the highest-precedence variable layer (see eva-004).
verify.sh — post-send
Executed only when --send was passed AND the file exists AND is executable. Receives:
- All
EVA_VAR_*env vars from before the send. EVA_OUTPUT_FILEpointing at a tempfile containing claude’s full stdout (tee’d atbin/kick:196).
Exit code maps to the verified field in .usage.jsonl:
| verify.sh exit | verified |
|---|---|
| 0 | true |
| non-zero | false |
| no verify.sh present | null |
This mapping powers the promotion gates in eva-003: tested → ready requires verified == true strictly.
Sequence
sequenceDiagram participant K as bin/kick participant G as guard.sh participant C as claude participant T as tempfile participant V as verify.sh participant L as .usage.jsonl
K->>G: exec (EVA_VAR_* exported) alt guard exit ≠ 0 G-->>K: stderr K-->>K: die — no usage row else guard exit 0 G-->>K: KEY=VALUE on stdout K->>K: VARS += guard_lines K->>C: pipe rendered prompt C-->>T: stdout tee'd K->>V: EVA_OUTPUT_FILE=tempfile V-->>K: exit 0|nonzero K->>L: append row with verified=true|false|null endKey Terms
- EVA_OUTPUT_FILE — absolute path to the temp file holding claude’s full stdout for this send; only set during
verify.sh. - Guard overlay — the
KEY=VALUElines guard prints become the highest-precedence var layer for{{ }}substitution. - verified=null — sentinel meaning no verifier exists; counted as success for
testedgate but not forready(eva-003).
Q&A
Q: What does kick do when guard.sh exits non-zero?
A: Aborts immediately via die, with no .usage.jsonl append (bin/kick:175). The user sees guard.sh failed for <id> (exit <rc>) on stderr.
Q: Which environment variable hands verify.sh the model output?
A: EVA_OUTPUT_FILE — a path to a mktemp tempfile that received claude’s stdout via tee (bin/kick:192-202).
Q: How is verify.sh’s exit code reflected in .usage.jsonl?
A: verified=true for exit 0, verified=false for non-zero, verified=null if no executable verify.sh exists (bin/kick:200-204).
Examples
A guard that refuses empty constraints and overlays a default model:
#!/usr/bin/env bash[[ -n "$EVA_VAR_constraints" ]] || { echo "constraints empty" >&2; exit 1; }echo "model_hint=claude-opus-4-7"neighbors on the map
- kick Variable Resolution Pipeline debugging why a {{var}} did not substitute
- .usage.jsonl Append Format computing prompt usage stats outside of eva show
- Run Outcome Classification interpreting a History row's status pill
- Operations & Versions Schema writing a new sync query