fix(#464): PR-create hooks resolve PR origin repo, not session ops-fork#465
Merged
Conversation
When the session is pinned to the ops-fork but `gh pr create --repo X` targets a sibling repo, two PreToolUse hooks evaluated the WRONG repo: - require-agdr-for-arch-pr.sh diffed the ops-fork working tree (showing framework paths .claude/migrations/, topologies/, handbooks/domain/) instead of the PR's actual diff, blocking even with a valid AgDR. - validate-pr-create.sh used the ops-fork's `upstream` remote as a ticket- existence fallback for the sibling PR, causing false "ticket not found" blocks when a premium ticket number collided with a closed framework issue. Fix: - Add _lib-pr-repo.sh with `pr_repo_matches_cwd` helper: parses --repo from the command, resolves `git remote get-url origin` for the cwd, compares them. Handles SSH + HTTPS URL forms on macOS (BSD sed-safe patterns). - require-agdr-for-arch-pr.sh: exit 0 early when the PR targets a repo that differs from the current git working tree's origin — the diff would be meaningless from the wrong cwd. - validate-pr-create.sh: suppress the upstream-remote fallback when CMD_REPO is set and the target repo belongs to a different project lineage (matches neither origin nor its upstream). Framework gating preserved: - Framework PRs (--repo = ops-fork origin) still diff the framework tree and block on arch-path changes without an AgDR. - Fork → upstream PRs (#207) still use the upstream fallback when CMD_REPO matches origin or upstream of the current working tree. Tests: test_pr_hooks_cross_repo.sh — 9 cases covering A1-A4 (agdr hook) and B1-B5 (validate hook), including both the fix scenarios and regressions. Refs #464 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…th test Finding 1 — silent revert on missing lib: require-agdr-for-arch-pr.sh: when _lib-pr-repo.sh is absent, emit a loud WARN naming the missing lib and that repo-resolution is degraded. The hook now falls through to the diff check visibly rather than silently skipping the guard entirely (which re-opened bug #464 invisibly). validate-pr-create.sh: same shape — the cross-repo guard's else branch (inline fallback) now emits a WARN before proceeding. The guard logic is preserved via the inline fallback, but the degraded state is no longer silent. Finding 2 — test doesn't exercise the new guard allow-path: test_pr_hooks_cross_repo.sh B6: new test where --repo=<fork-origin>, ticket exists only in upstream, and the upstream remote is configured. CMD_REPO_LC == ORIGIN_LC → guard's allow-path fires → upstream fallback permitted → PASS. A deliberate bug in the guard (wrong variable in the comparison) makes B6 FAIL. Verified fails-before/passes-after. Added C1 + C2: assert the WARN is emitted when the lib is missing. Finding 3 (optional, done cheaply): _lib-pr-repo.sh pr_repo_matches_cwd: when git_origin_repo returns empty (no origin remote), emit a WARN before returning 0 (safe default) so the "no origin → treat as same-repo" assumption is visible, not silent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… parsers (F1, F2, F3)
F1 — all four --repo/-R forms now parsed in all three parsers:
- _lib-pr-repo.sh: pr_cmd_target_repo() extended to match
--repo VALUE, --repo=VALUE, -R VALUE, and -R=VALUE. The
BSD-sed alternation-capture-group approach (^|[[:space:]]) is
replaced with the greedy `.*[[:space:]]<FLAG>` form which
correctly extracts only the value on macOS sed -E.
- validate-pr-create.sh: CMD_REPO extracted via pr_cmd_target_repo
from _lib-pr-repo.sh (DRY); inline fallback updated with the
same four-form logic for partial-checkout environments.
- require-agdr-for-arch-pr.sh: CMD_REPO_PRESENT grep updated to
match all four forms via `(--repo[=[:space:]]|-R[=[:space:]])`.
New tests: A5/A6/A7 (agdr hook) and B7/B8/B9 (validate hook)
confirm the guard fires for each form; all six tests fail when the
parser is intentionally broken to only handle --repo VALUE.
F2 — C1 in test_pr_hooks_cross_repo.sh refactored to sandbox-copy
pattern (same as C2). The previous C1 used mv to remove
_lib-pr-repo.sh from $REPO_ROOT/.claude/hooks/ and relied on mv
back after the test; a SIGKILL between the two mv calls would
permanently remove the lib from the live hooks directory. C1 now
copies the hook + its deps into a temp sandbox, removes the lib
from the sandbox copy only, and never touches $REPO_ROOT/.claude/.
F3 — Two stale comments in C1 ("exits 0") updated to "BLOCK / rc=2"
to match the actual expected behavior asserted by the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
atlas-apex
commented
May 30, 2026
atlas-apex
left a comment
Collaborator
Author
There was a problem hiding this comment.
Code Review: PR #465 — APPROVED (posted as comment; cannot self-approve via GitHub)
Commit: 0428ef6f7e0636ba2edf34a8ec1f36af8505f3ef
Independent review (prior rounds were self-reviews). Focus per the #1 risk: confirming the framework's own gating is NOT weakened.
Summary
Adds _lib-pr-repo.sh (shared --repo/-R parser + origin-comparison helper) and wires it into require-agdr-for-arch-pr.sh (cross-repo diff guard) and validate-pr-create.sh (cross-repo tracker-fallback suppression), fixing false-positive blocks on sibling-repo PRs (#464) without loosening framework PR gating.
Checklist Results
- Architecture & Design: Pass
- Code Quality: Pass — BSD-sed-safe, well-commented rationale
- Testing: Pass — 18/18 new cases; regression + F1-bypass coverage verified
- Security: Pass (one advisory below)
- Performance: Pass (N/A)
- PR Description & Glossary: Pass
- Technical Decisions (AgDR): N/A — bugfix to existing hooks; touches only
.claude/hooks/**(no arch trigger paths, no new deps);<!-- agdr: not-applicable -->correctly present - Adopter Handbooks: N/A — no handbooks loaded
Independently verified
- Framework gating preserved (#1 risk). Traced both hooks: a framework/ops-fork PR (no
--repo, or--repo me2resh/apexyardmatching origin) →pr_repo_matches_cwdreturns 0 → guard does NOT fire → framework-tree diff + framework tracker + AgDR run as before. Confirmed by A2/A4 (still BLOCK on missing AgDR) and B4 (framework ticket still PASSes). - All four
--repoforms parse (--repo V,--repo=V,-R V,-R=V, + host-prefix stripping). Proved F1 tests (A5/A6/A7/B7/B8/B9) are real guards: under a simulated space-only parser regression, all six FAIL. - No command injection — parsed values flow only into sed/grep/string comparison, never eval or unquoted exec.
- C1/C2 sandbox correctly — both copy hook+libs into a temp dir and
rm -fonly the sandbox copy; the live.claude/hooks/_lib-pr-repo.shis never mutated (confirmed clean post-run). - Lib-missing = visible WARN, not silent revert — C1 (agdr) and C2 (validate) pass.
- #207 fork→upstream fallback preserved — B5 (fork→upstream) and B6 (guard allow-path) pass.
- shellcheck clean / CI green — 4/4 checks pass; remaining shellcheck notes are info/style only (pre-existing, CI-tolerated).
- The one failing case in
test_require_agdr_for_arch_pr.sh(spike active-ticket marker) is confirmed identical on thedevbaseline — pre-existing, not a regression. Accurately disclosed in the PR body.
Issues Found
None blocking.
Suggestions
- suggestion (low severity, non-blocking): greedy body-token collision in
pr_cmd_target_repo. When a command places--repo <origin>before a--bodywhose text contains the literal token--repo(e.g. prose like "fixes the --repo parsing bug"), the greedy last-match sed extracts a garbage slug from the body.pr_repo_matches_cwdthen returns 1 (cross-repo), so both hooks skip gating on a legitimate same-repo PR — a weakening direction, not a false-block. Likelihood is low; the lib's own comment (lines 80-83) anticipates this (harness position-normalises--repobefore--body). The four-flag-form coverage is unaffected. Optional hardening: extract--repoonly from the pre---body/pre---titlecommand segment, or stop the greedy match at the first flag boundary. Fine to defer — not introduced by this PR.
Verdict
APPROVED (recorded as a comment because GitHub blocks self-approval; the Rex approval marker is being written.)
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 0428ef6f7e0636ba2edf34a8ec1f36af8505f3ef
This was referenced May 30, 2026
Closed
me2resh
added a commit
that referenced
this pull request
Jun 5, 2026
…rk (#465) * fix(#464): PR-create hooks resolve PR origin repo, not session ops-fork When the session is pinned to the ops-fork but `gh pr create --repo X` targets a sibling repo, two PreToolUse hooks evaluated the WRONG repo: - require-agdr-for-arch-pr.sh diffed the ops-fork working tree (showing framework paths .claude/migrations/, topologies/, handbooks/domain/) instead of the PR's actual diff, blocking even with a valid AgDR. - validate-pr-create.sh used the ops-fork's `upstream` remote as a ticket- existence fallback for the sibling PR, causing false "ticket not found" blocks when a premium ticket number collided with a closed framework issue. Fix: - Add _lib-pr-repo.sh with `pr_repo_matches_cwd` helper: parses --repo from the command, resolves `git remote get-url origin` for the cwd, compares them. Handles SSH + HTTPS URL forms on macOS (BSD sed-safe patterns). - require-agdr-for-arch-pr.sh: exit 0 early when the PR targets a repo that differs from the current git working tree's origin — the diff would be meaningless from the wrong cwd. - validate-pr-create.sh: suppress the upstream-remote fallback when CMD_REPO is set and the target repo belongs to a different project lineage (matches neither origin nor its upstream). Framework gating preserved: - Framework PRs (--repo = ops-fork origin) still diff the framework tree and block on arch-path changes without an AgDR. - Fork → upstream PRs (#207) still use the upstream fallback when CMD_REPO matches origin or upstream of the current working tree. Tests: test_pr_hooks_cross_repo.sh — 9 cases covering A1-A4 (agdr hook) and B1-B5 (validate hook), including both the fix scenarios and regressions. Refs #464 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#464): harden cross-repo guard — WARN on missing lib, B6 allow-path test Finding 1 — silent revert on missing lib: require-agdr-for-arch-pr.sh: when _lib-pr-repo.sh is absent, emit a loud WARN naming the missing lib and that repo-resolution is degraded. The hook now falls through to the diff check visibly rather than silently skipping the guard entirely (which re-opened bug #464 invisibly). validate-pr-create.sh: same shape — the cross-repo guard's else branch (inline fallback) now emits a WARN before proceeding. The guard logic is preserved via the inline fallback, but the degraded state is no longer silent. Finding 2 — test doesn't exercise the new guard allow-path: test_pr_hooks_cross_repo.sh B6: new test where --repo=<fork-origin>, ticket exists only in upstream, and the upstream remote is configured. CMD_REPO_LC == ORIGIN_LC → guard's allow-path fires → upstream fallback permitted → PASS. A deliberate bug in the guard (wrong variable in the comparison) makes B6 FAIL. Verified fails-before/passes-after. Added C1 + C2: assert the WARN is emitted when the lib is missing. Finding 3 (optional, done cheaply): _lib-pr-repo.sh pr_repo_matches_cwd: when git_origin_repo returns empty (no origin remote), emit a WARN before returning 0 (safe default) so the "no origin → treat as same-repo" assumption is visible, not silent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#464): handle --repo=VALUE, -R VALUE, -R=VALUE forms in all three parsers (F1, F2, F3) F1 — all four --repo/-R forms now parsed in all three parsers: - _lib-pr-repo.sh: pr_cmd_target_repo() extended to match --repo VALUE, --repo=VALUE, -R VALUE, and -R=VALUE. The BSD-sed alternation-capture-group approach (^|[[:space:]]) is replaced with the greedy `.*[[:space:]]<FLAG>` form which correctly extracts only the value on macOS sed -E. - validate-pr-create.sh: CMD_REPO extracted via pr_cmd_target_repo from _lib-pr-repo.sh (DRY); inline fallback updated with the same four-form logic for partial-checkout environments. - require-agdr-for-arch-pr.sh: CMD_REPO_PRESENT grep updated to match all four forms via `(--repo[=[:space:]]|-R[=[:space:]])`. New tests: A5/A6/A7 (agdr hook) and B7/B8/B9 (validate hook) confirm the guard fires for each form; all six tests fail when the parser is intentionally broken to only handle --repo VALUE. F2 — C1 in test_pr_hooks_cross_repo.sh refactored to sandbox-copy pattern (same as C2). The previous C1 used mv to remove _lib-pr-repo.sh from $REPO_ROOT/.claude/hooks/ and relied on mv back after the test; a SIGKILL between the two mv calls would permanently remove the lib from the live hooks directory. C1 now copies the hook + its deps into a temp sandbox, removes the lib from the sandbox copy only, and never touches $REPO_ROOT/.claude/. F3 — Two stale comments in C1 ("exits 0") updated to "BLOCK / rc=2" to match the actual expected behavior asserted by the test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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
gh pr create --repo Xtargets a sibling repo (e.g.me2resh/apexyard-premium),require-agdr-for-arch-pr.shwas diffing the ops-fork working tree (showing framework paths.claude/migrations/,topologies/,handbooks/domain/) instead of the PR's actual diff, blocking even when the PR had a valid AgDR.upstreamremote (me2resh/apexyard) as a ticket-existence fallback for sibling-repo PRs; a premium ticket number colliding with a closed/missing framework issue caused false "ticket not found" blocks._lib-pr-repo.sh— shared helper that parses--repofrom the command, resolvesgit remote get-url origin(BSD-sed-safe for macOS), and compares them to detect a cross-repo create context; sourced by both hooks.Testing
test_pr_hooks_cross_repo.sh— 9 new cases: A1–A4 (agdr hook cross-repo guard + regressions) and B1–B5 (validate hook cross-repo tracker suppression + regressions)test_require_agdr_for_arch_pr.sh— 12 pass, 1 pre-existing failure (spike-marker test, exists ondevbaseline before this PR)test_validate_pr_create_upstream.sh— 7 pass, 0 failtest_validate_pr_create_head.sh— 8 pass, 0 failtest_single_closes_per_pr.sh,test_detect_role_trigger.sh,test_validate_pr_required_sections.sh) — all pass, no regressions introducedbash -n) on all 4 changed/new filesRefs #464
Glossary
me2resh/apexyard-premium) that lives in a separate git clone, not inside the ops-fork#207feature that rechecks a ticket against theupstreamgit remote when the primary lookup misses — only valid within the same project lineage_lib-pr-repo.shpr_repo_matches_cwd— returns 0 when the PR target repo matches the current working tree's origin, 1 otherwise