Skip to content

date: 2026-06-19 tags: [hooks, gate, enforcement, brainstorm, test-fixtures] status: active graduated_to:

A wired gate that matches the wrong case never fires — and a case-mismatched fixture hides it

Symptom — The brainstorm review-export gate was supposed to block advancing a stage until its HTML review export existed, but real brainstorms advanced freely without it — the likely root cause of the long-standing "the agent keeps skipping the HTML review" complaint. Yet gate.test.sh was green the whole time.

Root causegate.conf listed the required stages capitalised (REVIEW_REQUIRED_STAGES=Define,Develop,Deliver) and the hook matched them case-sensitively, but real docs write lowercase stage: define. The match never hit → the gate returned "allow" for every real doc. It was invisible because the test fixtures also used capitalised stages, so the suite exercised a path real data never takes — a fixture that didn't match the real data shape validated a dead gate.

Fixcaa1dd6: a case-insensitive, multi-artifact REQUIRE_<stage> mechanism in .claude/hooks/gate.sh (require_missing() lowercases the stage); gate.test.sh rewritten to use lowercase fixtures (real shape) + an explicit casing-bug regression case. (A neighbouring single-item read-EOF bug — printf '%s' vs '%s\n' — was masking single-artifact stages the same way; fixed in the same SHA.)

Guardgate.test.sh now builds fixtures at lowercase stages (make_req_fixture) so a future case-only match fails the suite; the "casing-bug regression" case asserts a lowercase define blocks without its artifacts. Two conventions worth holding (→ .claude/rules/testing.md if they recur):

  1. An enforcement test fixture must use the same shape/casing as real data, or it validates a path production never takes.
  2. "Wired" ≠ "fires." Verify a gate/hook with a live trigger (here: a real PreToolUse block test confirmed gate.sh actually denies), not by reading the config — this session, that "verify, don't assume" move caught three separate false assumptions (this gate, whether the SKILL body loads, whether the hook fires in the runtime).