Three related defects found while diagnosing reCode#128, all in the gate path:
1. Consumer repos run the Stop gate twice
The scaffolded .claude/settings.json in consumer repos registers gate.sh test_affected as a Stop hook — and the plugin's own hooks.json registers the identical Stop hook again (${CLAUDE_PLUGIN_ROOT}/scripts/gate.sh test_affected). Every turn in a consumer repo pays the gate twice (in reDeploy that's 2 × ~90 s of full build+test). reCode itself only runs it once (local settings, no plugin). Either the plugin's hooks.json or the scaffolded settings hook should own this — not both.
2. gate.sh's pnpm-lock preflight runs before the "empty gate ⇒ skip" branch
In .claude/scripts/gate.sh, the lockfile-sync check (cmp pnpm-lock.yaml node_modules/.pnpm/lock.yaml → exit 1) executes before the gates.json lookup that skips empty/unconfigured gates. A deliberately blanked gate ("test_affected": "" — the documented way to mark a gate not-configured) can therefore still fail the hook whenever node_modules is momentarily out of sync. The preflight belongs after the skip branch (or should only run for gates that will actually execute).
3. test_affected = full suite makes the Stop hook unaffordable
The reDeploy adapter's _note already admits test_affected "currently runs the full suite … refine later". In practice that is pnpm -r build && pnpm -r test && forge test (~90 s, all cores) after every turn, doubled by defect 1, plus the same suite burned by each loop-daemon tick. Besides the cost, a multi-minute Stop hook interacts badly with headless sessions (see #128: parents abandon slow children; orphaned gates die of SIGPIPE mid-report). The scaffold should ship an affected-scoped default (changed-package filter) or a hard time budget for Stop-hook gates.
All three were confirmed by direct observation (hooks.json inspection, gate.sh source, strace of a headless session whose gate finished green after its session was already gone).
Three related defects found while diagnosing reCode#128, all in the gate path:
1. Consumer repos run the Stop gate twice
The scaffolded
.claude/settings.jsonin consumer repos registersgate.sh test_affectedas a Stop hook — and the plugin's ownhooks.jsonregisters the identical Stop hook again (${CLAUDE_PLUGIN_ROOT}/scripts/gate.sh test_affected). Every turn in a consumer repo pays the gate twice (in reDeploy that's 2 × ~90 s of full build+test). reCode itself only runs it once (local settings, no plugin). Either the plugin's hooks.json or the scaffolded settings hook should own this — not both.2.
gate.sh's pnpm-lock preflight runs before the "empty gate ⇒ skip" branchIn
.claude/scripts/gate.sh, the lockfile-sync check (cmp pnpm-lock.yaml node_modules/.pnpm/lock.yaml→exit 1) executes before the gates.json lookup that skips empty/unconfigured gates. A deliberately blanked gate ("test_affected": ""— the documented way to mark a gate not-configured) can therefore still fail the hook whenever node_modules is momentarily out of sync. The preflight belongs after the skip branch (or should only run for gates that will actually execute).3.
test_affected= full suite makes the Stop hook unaffordableThe reDeploy adapter's
_notealready admitstest_affected"currently runs the full suite … refine later". In practice that ispnpm -r build && pnpm -r test && forge test(~90 s, all cores) after every turn, doubled by defect 1, plus the same suite burned by each loop-daemon tick. Besides the cost, a multi-minute Stop hook interacts badly with headless sessions (see #128: parents abandon slow children; orphaned gates die of SIGPIPE mid-report). The scaffold should ship an affected-scoped default (changed-package filter) or a hard time budget for Stop-hook gates.All three were confirmed by direct observation (hooks.json inspection, gate.sh source, strace of a headless session whose gate finished green after its session was already gone).