You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
.claude/hooks/_lib-extract-pr.sh → extract_pr_number() (used by block-unreviewed-merge.sh, require-design-review-for-ui.sh, require-architecture-review.sh, block-merge-on-red-ci.sh)
Given / When / Then
Given an approved PR (#27) with valid Rex + design + CEO markers at HEAD When the merge is run as gh pr merge $PR --repo "$REPO" --squash --delete-branch 2>&1 | tail -5 (PR number passed via an unexpanded shell variable, with a 2>&1 stderr redirect) Then the gate blocks claiming "PR #2 has no recorded code-reviewer approval" and looks for …__2-rex.approved — expected: it should resolve PR #27 (or fail safe to the gh pr view fallback), not silently target a different PR number scraped from the redirect.
The hook receives the literal string gh pr merge $PR … 2>&1 | tail -5. Step 2 of extract_pr_number matches gh pr merge up to the first | (span [^|;&]* includes 2>&1), then grep -oE '[0-9]+' | head -1 returns 2.
major (wrong result — blocks the correct merge / could mis-target another PR; trivial workaround exists)
Notes
Two compounding causes: (1) the extractor parses the raw command string, so a $VAR PR arg is never the real number; (2) when no bare integer follows merge, the [^|;&]* span greedily catches the 2 in 2>&1. Suggested fix: strip redirection tokens ([0-9]*>&?[0-9]*, &>, >>) from the span before number extraction, and/or when the first post-merge token isn't a bare integer, fall through to the step-3 gh pr view fallback instead of returning a stray digit. A test case with a 2>&1 redirect + variable PR arg would lock the regression.
Glossary
Term
Definition
Merge gate
PreToolUse hooks that block gh pr merge until required approval markers exist at the PR HEAD.
PR extractor
extract_pr_number() in _lib-extract-pr.sh — parses the PR number from the merge command string.
Stderr redirect (2>&1)
Shell syntax sending file descriptor 2 (stderr) to fd 1 (stdout); the 2 is a fd number, not a PR number.
Affected
.claude/hooks/_lib-extract-pr.sh→extract_pr_number()(used byblock-unreviewed-merge.sh,require-design-review-for-ui.sh,require-architecture-review.sh,block-merge-on-red-ci.sh)Given / When / Then
Given an approved PR (#27) with valid Rex + design + CEO markers at HEAD
When the merge is run as
gh pr merge $PR --repo "$REPO" --squash --delete-branch 2>&1 | tail -5(PR number passed via an unexpanded shell variable, with a2>&1stderr redirect)Then the gate blocks claiming "PR #2 has no recorded code-reviewer approval" and looks for
…__2-rex.approved— expected: it should resolve PR #27 (or fail safe to thegh pr viewfallback), not silently target a different PR number scraped from the redirect.Repro
PR=27; gh pr merge $PR --repo <project-repo> --squash --delete-branch 2>&1 | tail -5gh pr merge $PR … 2>&1 | tail -5. Step 2 ofextract_pr_numbermatchesgh pr mergeup to the first|(span[^|;&]*includes2>&1), thengrep -oE '[0-9]+' | head -1returns2.2>&1→gh pr merge 27 --repo <project-repo> --squash --delete-branch.Framework version
v2.3.0
Severity
major (wrong result — blocks the correct merge / could mis-target another PR; trivial workaround exists)
Notes
Two compounding causes: (1) the extractor parses the raw command string, so a
$VARPR arg is never the real number; (2) when no bare integer followsmerge, the[^|;&]*span greedily catches the2in2>&1. Suggested fix: strip redirection tokens ([0-9]*>&?[0-9]*,&>,>>) from the span before number extraction, and/or when the first post-mergetoken isn't a bare integer, fall through to the step-3gh pr viewfallback instead of returning a stray digit. A test case with a2>&1redirect + variable PR arg would lock the regression.Glossary
gh pr mergeuntil required approval markers exist at the PR HEAD.extract_pr_number()in_lib-extract-pr.sh— parses the PR number from the merge command string.2>&1)2is a fd number, not a PR number.