Repository navigation
fix(agents): key review markers to the reviewed commit and worktree - #755
Conversation
The pre-push review gate could attest to code no reviewer read. review-marker.sh keyed the marker to whatever HEAD the session's launch directory had when the reviewer stopped, so a commit made while a reviewer ran inherited its ship_it, and a review of a sibling worktree marked the launch directory's branch instead. Reviewers now end with `REVIEWED: <sha>` naming the commit they pinned before reading anything. The hook writes the marker into each worktree on that commit, and only when that worktree's HEAD reflog shows it on the commit from the start of the review (the subagent transcript's first timestamp) until now. Same-commit entries such as `git reset` or `git stash` don't count as moves. A report delivered through the subagent hand-back leaves only closing text in last_assistant_message, so the hook reads the hand-back's message from the transcript. Every decision goes to stderr and to tmp/review-marker.log, which the push gate prints when it blocks. agent-bash-gate.sh checked HEAD of the session's launch directory for every push. It now follows the push to where it runs (the tool call's cwd, a preceding cd/pushd, git -C), resolves each refspec source to the commit it publishes, and looks for that commit's markers in every worktree of the repository. It blocks when it can't tell: a computed directory or refspec, GIT_DIR, --all/--mirror, a glob refspec, a wrapped push, or a payload without cwd. A push into another repository with no reviewer manifest is outside the policy. Pushes through `git -C` and from a detached HEAD were not gated at all before. skip-pre-push-review.sh writes to the worktree root when run from a subdirectory. .claude/hooks/review-gate.test.sh drives both hooks with synthetic events in a scratch repository with sibling worktrees; it is a make verify leaf (test-review-gate). Because the pre-commit hook runs it with GIT_DIR exported, it clears git's repository variables, names every repository with -C, and refuses to write unless git sees the scratch directory as outside any repository and creates each scratch repository where asked. /prepush, the reviewer agents, AGENTS.md and the Claude Code docs page describe the REVIEWED line and the rule not to move HEAD while reviewers run. Closes #454 Closes #565 Closes #659 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cheap filter in agent-bash-gate.sh strips quotes one line at a time, so a heredoc body, a multi-line quoted string or a comment that mentions `git push` reached the tokenizer, which correctly found no push, and the gate then refused the command as one it couldn't follow. It now refuses only when the tokens hold a `git … push` the walk didn't gate: behind an unknown wrapper, or inside a command substitution. The gate runs before the command line does, so in `git commit … && git push` it judged the commit before the new one. A push that follows a HEAD-moving git command (commit, merge, rebase, reset, checkout, switch, cherry-pick, revert, am, pull) on the same line is now blocked with "push in a separate command". Pushes inside if/while/until/for … do are followed instead of refused, and the matching refspec `:` (or `+:`) is blocked like --all instead of being read as a delete. review-marker.sh uses the hand-back message whenever the final message lacks either the VERDICT or the REVIEWED line, retries the transcript read once, and resolves an abbreviated REVIEWED sha that names exactly one commit. A ship_it without a REVIEWED line, which is what a session started before this change produces because reviewer definitions load at session start, logs how to recover, and the gate's block message says the same and now shows the latest decisions from every worktree's log. pre-push-reviewer pins its commit in step 1 with `git -C <path> rev-parse HEAD`. The docs say the gate checks the commit each refspec publishes (its tip, or HEAD with no refspec), and the remaining copies that described a VERDICT-only, HEAD-keyed marker now describe REVIEWED plus VERDICT and the reviewed commit. The test covers each change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agent-bash-gate.sh looked for an unfollowed push only when it had followed none, so a second push on the same line behind a wrapper or in a command substitution went unchecked once the first was gated. The walk now records each simple command it gates, and every other one is checked. GIT_DIR or GIT_WORK_TREE exported, declared or assigned earlier on the line now makes the push's repository unknown, the same as the inline form. The test requires jq up front, and development.md lists jq among the prerequisites, since make verify now runs it. pr-review-locally names the PR worktree's path in its reviewer prompts, because reviewers pin the worktree their prompt names, else their current directory. The reviewer agents say an unresolvable REVIEWED sha means no marker, and the remaining "marker for HEAD" phrasings say "for the commit being pushed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four gaps in the push gate, each now covered by review-gate.test.sh: - Every check fed the command to `grep -q` through a pipe under pipefail. Past a pipe buffer (64 KB), grep's early exit sent SIGPIPE to the writer and the pipeline read as no match, so a long `gh pr create` without --draft, or a push ahead of a long heredoc body, went unchecked. The checks now read here-strings. - An exported GIT_DIR or GIT_WORK_TREE shared a variable with "directory unknown", so a later absolute cd, pushd or git -C cleared it. It is tracked on its own now; only unset or the end of a subshell clears it. - A HEAD-moving git command counted only when run directly, so `timeout 60 git commit … && git push` or `out=$(git commit …) && git push` had the push judged on the commit before the move. A wrapped or substituted one counts too. - A push in code handed to a shell (bash -c, sh -c, eval, a heredoc or here-string read by a shell) was never seen, and one in a process substitution was dropped as a redirect target. The first now blocks as a push the gate can't follow; the second is followed like any other push. The Claude Code docs now say what a gating reviewer must end its report with for its ship_it to write a marker, matching AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three more gaps in the push gate, each covered by review-gate.test.sh: - A substitution in a here-string or a redirect target was dropped with its word, so `grep -q x <<< "$(git -C ../wt-b push)"` or `echo hi > "$(git push …)"` went unchecked, and a commit in one didn't count as a HEAD move. The tokenizer now keeps the text of every $(…) and `…` wherever its word goes, and the push and HEAD-move checks scan all of it. A quoted substitution no longer slips past the cheap pre-filter either. - Inside $(…), a here-string's text was glued to `<<<`, so a push in `$(sh <<< 'git push')` didn't read as one. - Code handed to a shell was scanned without joining backslash-newline continuations, so a push split across lines in `bash -c` or a heredoc read by bash went through. AGENTS.md, the Claude Code docs and the changelog now say the gate reads only the command line, list what it blocks, and name the forms it doesn't follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last change scanned the text of every $(…) for a push, but with its quotes already stripped, so `n=$(grep -c 'git push' AGENTS.md)`, `git commit -m "$(echo 'explain the git push gate')"` and the like were refused as pushes the gate can't follow. The tokenizer now marks quoted text inside a $(…), and each substitution is checked with that text replaced by a placeholder; only a substitution that hands it to a shell (`$(bash -c '…')`, `$(sh <<<'…')`, `$(eval '…')`) is checked in full. Two forms the wider pre-filter now tokenizes failed to parse: a heredoc inside $(…) closed with `EOF)"` on one line, and an `$'…'` string holding `\'`. Both parse now; an apostrophe in a comment inside $(…) still doesn't, and the docs say such a line is refused. The test for the here-string spacing inside $(…) now uses `<<<'…'` with no space, so it fails without that fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two code paths from the last change had no test: stripping the quote
markers from a word's value, and reading an $'…' string inside $(…).
Each now has cases that fail when it is removed: a substitution's output
handed to eval or bash -c, and an $'…' string in a $(…), pushed or not.
The marker strip also runs in one pass with tr. Under bash 3.2,
${_w//…} took 47 s on a 200 KB substitution holding 200 quoted strings,
past the hook's time limit; it now takes about a second.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - docs/src/content/docs/development.md: kept origin/main's reworded make vulncheck row and this branch's make verify row (adds review-gate hook tests). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@coderabbitai review |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 SummarySummary by CodeRabbit
WalkthroughReview markers now identify the commit reviewed and are written only when worktree checks pass. The push gate resolves commits from supported push commands and checks for required reviewer markers. Behavioral tests and documentation cover the updated process. ChangesCommit-scoped pre-push review gate
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
actor Developer
participant Gate as agent-bash-gate.sh
participant Git
participant Markers as Worktree review markers
Developer->>Gate: Submit push command and execution directory
Gate->>Git: Resolve pushed refs and commit tips
Git-->>Gate: Return repository and commit details
Gate->>Git: Compare each commit with main or origin/main
Git-->>Gate: Return review-delta status
Gate->>Markers: Check reviewer markers for each commit
Markers-->>Gate: Return marker status
Gate-->>Developer: Allow or block the push
Suggested reviewers: Merge Risk: 🔵 Low · up to The changes tighten pre-push review markers to the reviewed commit. Two edge cases can still let unreviewed work through. A reviewer's later non-pass verdict can be overridden by an earlier pass. A branch created from unreviewed local main commits can skip review. Both are small fixes and worth addressing before relying on the gate. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 55.26% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 38 functions across 5 files. (11 skipped: 11 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Action performedReview finished.
|
|
📚 Docs preview is live → https://9ac60ca7-wavehouse-docs.wave-rf.workers.dev
|
Code Coverage OverviewLanguages: Go GoThe overall line coverage in commit 26e4f07 in the Show a line coverage summary of the most impacted files.
Updated |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: ASSERTIVE
- Plan: Advanced
- Run ID:
eb01fa15-ff41-4f87-b864-6d63c0d55c2a
📒 Files selected for processing (16)
.claude/agents/docs-reviewer.md.claude/agents/pre-push-reviewer.md.claude/commands/docs-review.md.claude/commands/prepush.md.claude/hooks/agent-bash-gate.sh.claude/hooks/review-gate.test.sh.claude/hooks/review-marker.sh.claude/skills/pr-review-locally/SKILL.md.github/prompts/docs-review.mdAGENTS.mdCHANGELOG.mdMakefiledocs/src/content/docs/claude-code.mddocs/src/content/docs/development.mdscripts/pre-push-reviewers.shscripts/skip-pre-push-review.sh
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
- GitHub Check: Docs preview
- GitHub Check: E2E tests
- GitHub Check: Coverage
- GitHub Check: Integration tests
🧰 Additional context used
📚 Code guidelines (5)
docs/src/content/docs/claude-code.md — auto-discovered
AGENTS.md — auto-discovered
.github/copilot-instructions.md — auto-discovered
CONTRIBUTING.md — auto-discovered
docs/src/content/docs/development.md — auto-discovered
📓 Path-based instructions (6)
Source excerpt: To add a command: drop a `.md` file in `.claude/commands/`.
📄 CodeRabbit inference engine (docs/src/content/docs/claude-code.md)
Files:
docs/src/content/docs/claude-code.md
Source excerpt: **Never hard-wrap prose.
📄 CodeRabbit inference engine (AGENTS.md)
Files:
docs/src/content/docs/development.mdCHANGELOG.mdAGENTS.mddocs/src/content/docs/claude-code.md
See [AGENTS.md](../AGENTS.md) for project conventions, architecture notes, and AI agent instructions.
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
AGENTS.md
Source excerpt: **Docs prose**: never hard-wrap Markdown — one paragraph is one line.
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Files:
docs/src/content/docs/development.mdCHANGELOG.mdAGENTS.mddocs/src/content/docs/claude-code.md
Source excerpt: Run `make fix` to apply them.
📄 CodeRabbit inference engine (docs/src/content/docs/development.md)
Files:
docs/src/content/docs/development.mdCHANGELOG.mdAGENTS.mddocs/src/content/docs/claude-code.md
Source excerpt: **Editors see WH001 in `.md` only.**
📄 CodeRabbit inference engine (AGENTS.md)
Files:
docs/src/content/docs/development.mdCHANGELOG.mdAGENTS.mddocs/src/content/docs/claude-code.md
🪛 ast-grep (0.45.3)
.claude/hooks/review-marker.sh
[warning] 83-83: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$1/tmp/review-marker.log"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
[warning] 183-183: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "📝 marker written: ${wt}/tmp/${agent_type}-passed-${short}"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
[warning] 185-185: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "failed to write ${wt}/tmp/${agent_type}-passed-${short} — no marker written."
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
.claude/hooks/review-gate.test.sh
[warning] 227-227: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$repo/tmp/review-marker.log"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
[warning] 229-229: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$repo/tmp/review-marker.log"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
[warning] 431-431: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$wtc/tmp/$R2-passed-"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
.claude/hooks/agent-bash-gate.sh
[warning] 654-654: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$wt/tmp/review-marker.log"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
[warning] 685-685: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: "$wt/tmp/review-skips-${sha}.log"
Note: [CWE-377] Insecure Temporary File.
(predictable-tmp-file-bash)
🪛 LanguageTool
.claude/commands/docs-review.md
[typographical] ~12-~12: Consider using an em dash in dialogues and enumerations.
Context: - empty → gating mode: the branch...
(DASH_RULE)
.claude/skills/pr-review-locally/SKILL.md
[style] ~43-~43: Since ownership is already implied, this phrasing may be redundant.
Context: ...r current directory, which may still be your own checkout. For example: ```js // all in...
(PRP_OWN)
.claude/agents/docs-reviewer.md
[typographical] ~14-~14: Consider using an em dash in dialogues and enumerations.
Context: - Gating mode — scope is **empty / de...
(DASH_RULE)
[style] ~36-~36: Consider using the typographical ellipsis character here instead.
Context: ...s another path). Diff against that sha (main...<sha>) and report exactly it on the `RE...
(ELLIPSIS)
[style] ~39-~39: Consider using the typographical ellipsis character here instead.
Context: ... but didn't" check)* — diff the branch (git diff --name-only main...<sha>) and walk the changed **code/conf...
(ELLIPSIS)
[style] ~67-~67: Since ownership is already implied, this phrasing may be redundant.
Context: ...y by the two parseable lines**, each on its own line: ```text REVIEWED: <the full 40-c...
(PRP_OWN)
CHANGELOG.md
[typographical] ~119-~119: Consider using an em dash in dialogues and enumerations.
Context: - **A pre-push review marker now attests ...
(DASH_RULE)
[uncategorized] ~119-~119: The official name of this software platform is spelled with a capital “H”.
Context: ...ude/skills/pr-review-locally/SKILL.md, .github/prompts/docs-review.md, scripts/{skip...
(GITHUB)
[style] ~119-~119: This sentence is over 40 words long. Consider splitting it up, as shorter sentences make the text easier to read.
Context: ...thub.com//issues/659). review-marker.sh keyed a reviewer's marker to whatever HEAD the session's launch directory had when the reviewer stopped, so a commit made while a reviewer ran inherited its ship_it, and a review of a sibling worktree marked the launch directory's branch instead. Reviewers now end with `REVIEWED: ...
(TOO_LONG_SENTENCE)
[style] ~119-~119: This sentence is over 40 words long. Consider splitting it up, as shorter sentences make the text easier to read.
Context: ... the launch directory's branch instead. Reviewers now end with REVIEWED: <sha>, the commit they pinned before reading anything (an unambiguous prefix will do), and the hook writes the marker into each worktree on that commit, and only when that worktree's HEAD reflog shows it on the commit from the start of the review to its end (git reset or git stash on the same commit doesn't count as a move). When last_assistant_message lacks e...
(TOO_LONG_SENTENCE)
[typographical] ~119-~119: The word ‘When’ starts a question. Add a question mark (“?”) at the end of the sentence.
Context: ...no longer has to be recorded as a skip. Each decision goes totmp/review-marke...
(WRB_QUESTION_MARK)
.claude/agents/pre-push-reviewer.md
[style] ~14-~14: Consider using the typographical ellipsis character here instead.
Context: ...is the local working state, computed as git diff main...<sha> for the commit you pin in step 1 ...
(ELLIPSIS)
[uncategorized] ~26-~26: The official name of this software platform is spelled with a capital “H”.
Context: ...d so it can refuse the marker. 2. Read .github/prompts/pr-review.md and AGENTS.md (...
(GITHUB)
[style] ~57-~57: Since ownership is already implied, this phrasing may be redundant.
Context: ...y by the two parseable lines**, each on its own line: ```text REVIEWED: <the ful...
(PRP_OWN)
.claude/commands/prepush.md
[style] ~33-~33: Consider using a more formal/concise alternative here.
Context: ...the reviewer name. To review a worktree other than your current directory, name its path i...
(OTHER_THAN)
AGENTS.md
[style] ~10-~10: This word has been used in one of the immediately preceding sentences. Using a synonym could make your text more interesting to read, unless the repetition is intentional.
Context: ...t ran returns ship_it. Every reviewer needs a marker for the commit being pushed — ...
(EN_REPEATEDWORDS_NEED)
[style] ~261-~261: Consider an alternative for the overused word “exactly”.
Context: ...e first push, before gh pr create, is exactly when the diff most needs review.) When...
(EXACTLY_PRECISELY)
[style] ~270-~270: Since ownership is already implied, this phrasing may be redundant.
Context: ...ide $(…), is refused: run the push as its own command. On VERDICT: iterate or `VERD...
(PRP_OWN)
docs/src/content/docs/claude-code.md
[uncategorized] ~83-~83: The official name of this software platform is spelled with a capital “H”.
Context: ... wt switch pr:<N>. Runs the canonical .github/prompts/pr-review.md workflow against ...
(GITHUB)
[uncategorized] ~84-~84: The official name of this software platform is spelled with a capital “H”.
Context: ...t changed but whose docs didn't), using .github/prompts/docs-review.md over the `scrip...
(GITHUB)
🪛 SkillSpector (2.11.2)
.claude/skills/pr-review-locally/SKILL.md
[warning] 75: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
🔇 Additional comments (16)
.claude/agents/docs-reviewer.md (1)
14-14: LGTM!Also applies to: 36-39, 67-74, 84-84
.claude/agents/pre-push-reviewer.md (1)
14-29: LGTM!Also applies to: 39-39, 57-64, 90-90
.claude/commands/docs-review.md (1)
12-12: LGTM!.claude/hooks/review-marker.sh (1)
140-193: LGTM!.claude/skills/pr-review-locally/SKILL.md (1)
31-31: LGTM!Also applies to: 43-50
.github/prompts/docs-review.md (1)
54-54: LGTM!AGENTS.md (1)
10-10: LGTM!Also applies to: 261-261, 270-270, 283-283, 308-308
docs/src/content/docs/claude-code.md (1)
27-28: LGTM!Also applies to: 53-60, 83-84, 109-109, 213-213
scripts/pre-push-reviewers.sh (1)
6-9: LGTM!.claude/commands/prepush.md (1)
6-10: LGTM!Also applies to: 33-35, 39-39, 52-57
.claude/hooks/agent-bash-gate.sh (1)
44-96: LGTM!Also applies to: 141-608, 690-781
scripts/skip-pre-push-review.sh (1)
31-36: LGTM!.claude/hooks/review-gate.test.sh (1)
1-454: LGTM!CHANGELOG.md (1)
119-119: LGTM!Makefile (1)
449-457: LGTM!Also applies to: 548-552, 566-566
docs/src/content/docs/development.md (1)
22-22: LGTM!Also applies to: 42-42, 531-531
Two review findings in the push gate and the marker hook, each with a test that fails without the fix: - The gate took local main as the base when it existed, so commits made on local main by mistake, then branched from (`git switch -c feat`), counted as already on the base and were pushed with no review. The base is now origin/main, and local main only when there's no remote-tracking ref; a stale origin/main only gates more. - The marker hook fell back to the hand-back report whenever the final message lacked either parseable line, so a final `VERDICT: iterate` without a REVIEWED line was replaced by an earlier hand-back's ship_it. It now falls back only when the final message has no verdict or says ship_it without naming the commit. The new-reviewer recipe in AGENTS.md and the Claude Code docs now says to pin with `git -C <worktree> rev-parse HEAD`, as the shipped reviewers do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header of review-marker.sh still said the final message is the report only when it carries both parseable lines. Since the last change it is the report whenever it has a verdict, unless that verdict is ship_it with no REVIEWED line, and a final iterate or block stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict resolution in docs/src/content/docs/development.md: the git/curl prerequisites row keeps both sides, so it is the git + curl + jq row with main's shellcheck and oldest-Node curl uses and this branch's jq rationale. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@coderabbitai approve All three findings from your review are fixed in ff0a491 (header comment in 36da460), and you confirmed each in its thread; 26e4f07 only merges — Posted by Claude Code on behalf of @EricAndrechek |
|
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! ✅ Action performedChanges approved. Review threads were not changed. |
…756) ## Summary The Integration job grew from about 3 to about 8 minutes with the distributed-backend tests and became CI's critical path (#701). This splits the suite into parts that CI runs as parallel jobs, runs the slow self-contained tests in parallel, and selects `internal/mq`'s integration tests by build tag instead of by name (#692). - **Parts, not shards.** `make test-integration` runs `test-integration-app` (`tests/integration`) and `test-integration-backends` (`internal/cache`, `internal/mq/natsspike`, `internal/mq`'s tagged tests) together. The `integration` job is a matrix over the Makefile's `INTEGRATION_PARTS`, which the `changes` job reads (`scripts/ci/integration-parts.sh` refuses anything but one `INTEGRATION_PARTS := …` line, and every part checks it against make's own list) and also turns into the fragment and producer lists the coverage job waits for. CI runs exactly the commands a local run does, a part added to the Makefile gets its own job with no workflow edit, and each part uploads `coverage-integration-<part>` for the coverage job to merge. Every Go job passes its own build-cache suffix (`-integration-<part>`, `-unit`, …), since two jobs on one key race to save it and a part would otherwise save a cache holding only its own test binaries. - **Parallel tests.** Ten slow tests in `tests/integration` that bring up their own servers or containers (shard claims, the NATS backend, the shared cache, boot resilience, the ClickHouse outage, the query errors against a stopped ClickHouse) call `t.Parallel()`, and the app part runs with `-parallel 8` since they mostly wait. `TestShardClaims_ScaleOneThreeTwo`, the dedupe DynamoDB tests and the tests that share the wired app are untouched. - **Nothing skipped silently.** `scripts/ci/tagged-tests.sh run` prints the tests, fuzz targets and examples `internal/mq`'s `integration`-tagged files declare, replacing the `-run '^Test(ExternalNATS|NewNATS|NATSPermissions_Refuse|Leases)'` pattern, and `tagged-tests.sh check`, which every part runs first, fails while a package with tagged tests is in no part. `scripts/ci/tagged-tests.test.sh` checks the selection against every entry `go test -list` prints on a throwaway module, and `scripts/ci/integration-parts.test.sh` checks the parts reading (both `make verify` leaves). - `internal/mq/natsspike` stays in the suite: it pins the nats-server behavior the external-NATS topology rests on, and it runs beside `internal/cache`, so it adds no wall-clock. ## Test plan Timings on a 4-vCPU amd64 Linux box (GitHub's runners have 4 vCPUs too), warm build cache: - [x] `make ci` green locally. - [x] Baseline on `main`: the two gotestsum calls took 339s + 58s = 397s (`tests/integration` 336.7s). - [x] `make test-integration COV_DEFER=1`: 221s, all pass. `tests/integration` 143.9s with the same 276 tests (test set diffed against the baseline run); backends part 76s (cache 14s alongside natsspike 17s, then `internal/mq` 56s, the same 217 + 95 tests). - [x] `test-integration-app` four more times: 143.9s to 154.9s. One run failed in `TestShardClaims_ScaleOneThreeTwo` at the one-writer check (#737); it fails the same way on `main` (the baseline run hit it), and it runs in the sequential phase, before any parallel test starts. - [x] Coverage: the integration profile covers 64.60% of statements against 64.59% for the baseline, and every block the baseline covered (9883 statements in both). - [x] #692: with a throwaway `//go:build integration` `TestMQNATSSomethingNew` in `internal/mq`, the old pattern lists 60 tests and misses it; the new selection lists 61. Without it, both select the same 60. - [x] Six more `test-integration-app` runs on a fresh 4-vCPU box: all pass (138–160s warm, 283s for the first, cold build). `-count=10` of the four parallel shard-claim tests together: 40 of 40 pass. - [x] `tagged-tests.test.sh` (it fails against the old name-only pattern), `integration-parts.test.sh`, `make check-integration-parts`, shellcheck and actionlint pass. - [x] Pre-push review: code `ship_it`; docs review `ship_it`. - [ ] CI: `Integration tests (app)` and `Integration tests (backends)` replace `Integration tests`, the coverage job finds both fragments, and the run takes about 3.5–4 minutes (it was 8–9). The first run on each new cache suffix compiles cold. Remaining findings: none. ## Related Issues Closes #701 Closes #692 Part of #740 Stacked on #755 (base `fix/prepush-gate-holes`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…782) ## Summary `make test-review-gate` failed intermittently in CI (four of the six `main` runs since #755, and one pull-request run) on two cases: `skip-pre-push-review.sh writes to the worktree root from a subdirectory` and `skipped reviewers satisfy the gate and are echoed`. The bug is in `scripts/skip-pre-push-review.sh`, not in the test or the hooks, and nothing about pushes to `main` causes it. The script checked the reviewer name with `bash scripts/pre-push-reviewers.sh | grep -Fxq -- "$name"` under `pipefail`. Bash writes each name in its own `write()` (one line per call under `strace` on bash 5.2), so `grep -q` can match `pre-push-reviewer` on the first line and exit before the second write. That write then dies of `SIGPIPE`, `pipefail` reports exit 141 as no match, and the script refuses: "'pre-push-reviewer' is not a reviewer in scripts/pre-push-reviewers.sh — refusing." Only the first-listed reviewer is affected. The test sent the script's stderr to `/dev/null`, so the CI log showed only a missing marker. - `scripts/skip-pre-push-review.sh` captures the list, then matches it from a here-string. Its advisory `git diff --name-only | grep -qv` check gets the same treatment. That check now resolves its base as the push gate has since #755 (`origin/main`, else `main`), which its comment already claimed. - `.claude/hooks/review-gate.test.sh` prints the skip script's stderr when a skip fails and requires both skipped reviewers' markers. It also adds a case whose manifest pauses after each name, so the reader has always exited before the next write. The old script fails that case on every run, on any machine. `review-marker.sh` has the same pipeline shape but puts `grep -E` in the middle. That `grep -E` buffers its output and writes it all at end of input, so its last `grep -q` never exits early. Run by hand against the same pausing manifest, that pipeline exited 0 every time on macOS and Linux, so the hook needs no change. The gate reads the whole list without `-q`. ## Test plan - [x] Reproduced on unchanged `main` (423f117) in `ubuntu:24.04` (bash 5.2) after a depth-1 clone with `main` checked out as a local branch, as `actions/checkout` does on a push. Idle, the suite passes. With 8 copies running at once on 4 pinned CPUs, 2 of 32 runs failed exactly the two CI cases. With the fix in place, 0 of 32 failed. - [x] Ran `skip-pre-push-review.sh pre-push-reviewer` 2000 times in 8 parallel loops on 4 CPUs (bash 5.2). The old script refused 35 times, every time with the message above. The fixed script refused 0 times. - [x] The new test case fails against the old script on bash 5.3, bash 3.2 and Linux bash 5.2 (136 ok, 1 FAIL that shows the refusal), and passes with the fix. - [x] `.claude/hooks/review-gate.test.sh`: all 137 cases pass under Homebrew bash 5.3 and under `/bin/bash` 3.2 with `/bin` first on `PATH`. - [x] `make lint-sh`, `make test-review-gate`, and `make verify` (pre-commit). ## Related Issues Closes #767. Part of #740. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
The pre-push review gate could attest to code no reviewer read.
review-marker.shused whatever HEAD the launch directory had when the reviewer stopped, so a commit made while a reviewer ran inherited itsship_it, and a review of a sibling worktree marked the launch directory's branch instead. Reviewers now end withREVIEWED: <sha>, the commit they pinned before reading anything (an unambiguous prefix will do). The hook writestmp/<name>-passed-<sha>into each worktree on that commit, and only when that worktree's HEAD reflog shows it on the commit from the start of the review (the reviewer transcript's first timestamp) to its end. Same-commit entries such asgit resetorgit stashdon't count as moves.last_assistant_messagelacks either line, as it does for a report delivered through the hand-back, the hook reads the hand-back message from the transcript. Every decision goes to stderr and totmp/review-marker.log, and a blocked push prints the latest decisions from every worktree's log.agent-bash-gate.shchecked the launch directory's HEAD whatever was pushed, so once that branch had markers a push from any sibling worktree went through, and a push throughgit -Cor from a detached HEAD was never checked. The gate now runs the command through a quote-aware tokenizer and follows the push to where it runs (the tool call'scwd, a precedingcd/pushd,git -C, insideif/while/for … door a process substitution). It requires markers for the commit each refspec publishes (its tip, or HEAD with no refspec), and a marker counts from any worktree of the repository. A command that only mentionsgit push, in a heredoc, a quoted string or a comment, is not a push.GIT_DIR/GIT_WORK_TREE(inline, assigned or exported earlier on the line; a latercdorgit -Cdoesn't undo it,unsetor leaving the subshell does),--all/--mirroror the matching refspec:, a glob refspec, a push behind a wrapper or in a command substitution, a push in code handed to a shell (bash -c,sh -c,eval, a heredoc or here-string read by a shell), or a payload withoutcwd. It also blocks a push that follows a HEAD-moving git command on the same line (git commit … && git push), because it would otherwise judge the commit before the move. It reads only the command line, so it guards against accidents, not a deliberate workaround. A push into another repository with no reviewer manifest is outside the policy.grep -qthrough a pipe underpipefail; past 64 KB the writer gotSIGPIPEwhengrepmatched early, which read as no match, so a longgh pr createwithout--draft, or a push ahead of a long heredoc body, went unchecked. The checks now use here-strings.skip-pre-push-review.shwrites to the worktree root when run from a subdirectory. The reviewer definitions,/prepush,/docs-review, the docs-review rubric, AGENTS.md and the hooks docs describe theREVIEWED:line, the "don't move HEAD while reviewers run" rule and "push in a separate command".pr-review-locallynames the PR worktree's path in its reviewer prompts.make verifynow needsjq, which the development prerequisites list.Rolling this out to an open Claude Code window
.claude/settings.jsondoesn't change, so a window rooted on a checkout with this commit uses the new hooks immediately..claude/agents/*.md) load when Claude Code starts. A window opened before this change runs reviewers that don't emitREVIEWED:, so theirship_itwrites no marker;tmp/review-marker.logand the gate's block message say so.git -C <worktree> rev-parse HEAD, and end your report withREVIEWED: <that sha>on the line beforeVERDICT:".git commit … &&.Test plan
make cigreen locally.make test-review-gate(newmake verifyleaf,.claude/hooks/review-gate.test.sh): 133/133 pass under bash 5.3 and macOS/bin/bash3.2. It feeds both hooks synthetic SubagentStop/PreToolUse JSON in a scratch repo with sibling worktrees, with pinned reflog and transcript clocks. Markers come only from the hook or the skip script.mainhooks fails 85 of 133, covering the commit-during-review, sibling-worktree, hand-back, refspec, fail-closed, shell-code and long-command cases.-C, and refuses to write unless its scratch repos land where asked. Under an inheritedGIT_DIRpointing at a scratch decoy, it passes and leaves the decoy untouched.make verifypasses in the pre-commit hook; shellcheck and markdownlint are clean.ship_it; docs reviewship_it./prepushround after merge should show📝 marker writtenlines intmp/review-marker.logfor both reviewers, including reports delivered through the hand-back.Remaining findings: none.
Related Issues
Closes #454
Closes #565
Closes #659
Part of #740
🤖 Generated with Claude Code