CRUMB a card from devarno-cloud

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 → kick aborts with die "guard.sh failed for $PROMPT_ID (exit $rc)" (bin/kick:175). No .usage.jsonl row is appended.
  • Stdout KEY=VALUE lines are parsed and appended to VARS, also re-exported as EVA_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_FILE pointing at a tempfile containing claude’s full stdout (tee’d at bin/kick:196).

Exit code maps to the verified field in .usage.jsonl:

verify.sh exitverified
0true
non-zerofalse
no verify.sh presentnull

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
end

Key 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=VALUE lines guard prints become the highest-precedence var layer for {{ }} substitution.
  • verified=null — sentinel meaning no verifier exists; counted as success for tested gate but not for ready (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