Merged
Conversation
added 4 commits
April 22, 2026 21:13
Replace the cache-attestation push/commit review-gate with a stateless
gate that runs `codex exec review --json` on every push and infers a
verdict from the streamed findings. No cache, no SHA matching, no
audit-receipt consultation — Codex is the source of truth, fresh every
push.
Flow:
git push → .husky/pre-push → rea hook push-gate
→ codex exec review --base <ref> --json
→ parse [P1]/[P2]/[P3] severity markers
→ write .rea/last-review.json + audit record
→ exit 0 (pass/empty/skip/disabled) / 1 (HALT) / 2 (blocked)
When blocked, Claude reads stderr + the structured last-review.json
and fixes — the organic retry loop IS the value.
Deleted (−26,692 LOC):
- hooks/push-review-gate.sh, push-review-gate-git.sh,
commit-review-gate.sh, _lib/push-review-core.sh
- src/hooks/review-gate/ (30 files: TS port Phase 1 + 2a + 2b)
- src/cli/cache.ts + src/cache/ (cache CLI surface)
- src/cli/audit.ts::runAuditRecordCodexReview
- src/audit/append.ts::appendCodexReviewAuditRecord
- 12 __tests__/hooks/push-review-*.test.ts files
- __tests__/cli/audit-record-codex.test.ts
- __tests__/readme-agent-workflow.test.ts
Added (+3,549 LOC):
- src/hooks/push-gate/ (7 modules: halt, policy, base, codex-runner,
findings, report, index) + 7 test modules
- src/cli/hook.ts — `rea hook push-gate [--base <ref>]` subcommand
- 0.11.0 policy migration in src/cli/upgrade.ts (strips removed
review.cache_max_age_seconds / review.allow_skip_in_ci fields,
backfills review.concerns_blocks: true)
- New markers v2 in src/cli/install/pre-push.ts; legacy v1 markers
still detected so upgrade migrates old installs cleanly
- .rea/last-review.json — atomic-write structured findings dump
(redact-pattern-scrubbed before disk hits)
Policy schema changes:
- REMOVED: review.cache_max_age_seconds, review.allow_skip_in_ci
- ADDED: review.concerns_blocks (bool, default true),
review.timeout_ms (int, default 600_000)
- KEPT: review.codex_required (semantics narrowed — "run codex
on push" not "consult audit log")
Env vars:
- NEW: REA_SKIP_PUSH_GATE=<reason> (value-carrying, audited)
- NEW: REA_ALLOW_CONCERNS=1 (per-push override)
- REMOVED: REA_SKIP_CODEX_REVIEW, REA_SKIP_PUSH_REVIEW
Audit events emitted by the gate (none read back):
- rea.push_gate.reviewed / halted / disabled / skipped / empty_diff
/ error
Tests: 907 pass, 1 skipped. Type-check, lint, build all green.
Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
Four findings from /codex-review against the 0.11.0 push-gate diff: P1 — Husky stub resolved `rea` via bare name, which fails for the documented `npx @bookedsolid/rea init` bootstrap (no persistent global install → `rea: not found` on every push). Now resolves in priority: `<root>/node_modules/.bin/rea` → PATH → `npx --no-install @bookedsolid/rea`, with a clear stderr error and exit 2 when none of those work. Body is identical in the installer template and the dogfood `.husky/pre-push`. P1 — `rea upgrade` was additive-only on `.claude/settings.json`, leaving the 0.10.x `push-review-gate.sh` / `commit-review-gate.sh` / `push-review-gate-git.sh` Bash-hook entries pointing at files this release just deleted. Added `pruneHookCommands()` to `settings-merge.ts` and a `STALE_HOOK_COMMAND_TOKENS` list in `upgrade.ts`. Upgrade now prunes first, then merges, and reports removed entries in the operator-facing warning output. P1 — The gate always diffed `HEAD` against the resolver ladder and ignored git's pre-push stdin, so `git push origin HEAD:release/1.0` silently reviewed against the wrong base (and short-circuited to empty-diff when the commit already existed on `main`). `runPushGate` now accepts a `refspecs: PrePushRefspec[]` dep, `parsePrePushStdin()` builds it from the git contract, and the CLI reads stdin with a 5s timeout. When stdin yields refspecs, the gate diffs `remote_sha..local_sha` per the actual push target; deletions and null-SHA new-ref cases fall back cleanly. P2 — `codex-runner.ts` resolved on the child's `exit` event, which can fire before the final stdout chunks are drained on large reviews. Switched to `close`, which waits for both stdio streams to end. Tests +12: parsePrePushStdin, refspec-aware runPushGate branches, pruneHookCommands. Tests: 919 pass, 1 skipped. Type-check, lint, build all green. Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
The 0.11.0 husky pre-push stub tries node_modules/.bin/rea → PATH → npx. That works in every consumer install, but rea's own repo has neither a node_modules copy of itself nor a persistent global install — the package IS the repo. The first push of feat/0.11.0-push-gate hit `rea: command not found` and aborted. Added a dist/cli/index.js fallback between the node_modules check and the PATH check: if dist/ is built (which it must be for the code under review to exist), invoke the CLI via `node <root>/dist/cli/index.js`. This makes the hook self-hosting for rea's own dogfood loop. The same body lives in src/cli/install/pre-push.ts (installer template) and .husky/pre-push (live dogfood hook); both updated in lockstep. Tests 919 pass, 1 skipped. Build + type-check + lint green. Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
Git passes `<remote-name> <remote-url>` to pre-push as positional argv; the husky stub forwards them with `"$@"`. Commander rejects any unexpected positional by default, so every real push hit: error: too many arguments for 'push-gate'. Expected 0 arguments but got 2. Declared the positional slot as a variadic `[gitArgs...]` and ignored it in the action handler. The base ref + refspecs still come from stdin + the auto-resolver; these positionals are informational only. Tests 919 pass. Build + type-check green. Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replace the cache-attestation push-review gate (
.claude/hooks/push-review-gate.sh,hooks/_lib/push-review-core.sh, the in-flight TS port atsrc/hooks/review-gate/,rea cachesubcommands,rea audit record codex-review) with a stateless pre-push Codex gate.Net LOC: +3,549 / −26,692 in production; test suite rebuilt on the smaller surface (919 tests pass).
What changes
git push→.husky/pre-push→rea hook push-gate→codex exec review --jsonagainst the actual push target (refspec-aware:remote_sha..local_sha) → parse[P1]/[P2]/[P3]severity markers →.rea/last-review.json+ audit record → exit 0/1/2.rea fix-then-pushwrapper.Breaking changes (single 0.11.0 changeset)
rea cache check|set|clear|list,rea audit record codex-reviewreview.cache_max_age_seconds,review.allow_skip_in_ci(rea upgradestrips them and writes a.bak-<ts>backup)REA_SKIP_CODEX_REVIEW,REA_SKIP_PUSH_REVIEWhooks/push-review-gate.sh,push-review-gate-git.sh,commit-review-gate.sh,hooks/_lib/push-review-core.sh(+.claude/mirrors).rea upgradeprunes the stale.claude/settings.jsonentries that referenced them.Added
rea hook push-gate [--base <ref>]— the single CLI entry point husky calls. Accepts git's positional pre-push argv silently; consumes refspecs from stdin with a 5s timeout.policy.review.concerns_blocks(bool, defaulttrue) — whentrue,[P2]findings block. Per-push override viaREA_ALLOW_CONCERNS=1.policy.review.timeout_ms(int, default 600_000) — hard cap on the Codex subprocess.REA_SKIP_PUSH_GATE=<reason>— value-carrying, audited; HALT still wins..rea/last-review.json— atomic-write structured dump, redact-scrubbed before disk.Adversarial review
Ran
/codex-reviewonce before push; Codex surfaced four findings (3× P1 + 1× P2):reaon PATH —npx @bookedsolid/rea initbootstrap leaves no persistent install →rea: not foundon every push. Fixed: stub resolves vianode_modules/.bin/rea→dist/cli/index.js(rea's own repo dogfood case) → PATH →npx --no-install.push-review-gate.sh. Fixed: addedpruneHookCommands()insettings-merge.ts+STALE_HOOK_COMMAND_TOKENSlist inupgrade.ts; upgrade now prunes first, merges second.git push origin HEAD:release/1.0reviewed against the wrong base (upstream ladder instead of the actual push target). Fixed:runPushGateaccepts arefspecs: PrePushRefspec[]dep; CLI parses stdin; gate diffsremote_sha..local_shaper refspec.exitand stdout drain —codex-runner.tscould parse a truncated buffer and misclassify a blocking review as pass. Fixed: switchedexit→close(waits for both stdio streams to end).All four addressed in commit
63322a5. Dogfood push of this branch exercised the new gate itself and passed cleanly.Test plan
pnpm lint— zero warnings, 12 redact patterns + 2 injection patterns clearedpnpm type-check— strict, zero errorspnpm test— 919 pass, 1 skipped (new push-gate modules: halt, policy, base, codex-runner, findings, report, index, pre-push installer rewrite, pruneHookCommands, refspec parsing)pnpm build— clean tscreview.codex_required: false(the repo's policy) per design — the full Codex path is proven by the unit tests.Follow-ups
rea upgradeagainst Helix's 0.10.x checkout and confirm migration is clean (policy backup + settings prune + husky refresh).