Skip to content

fix(#464): PR-create hooks resolve PR origin repo, not session ops-fork#465

Merged
atlas-apex merged 3 commits into
devfrom
fix/GH-464-pr-hooks-origin-repo
May 30, 2026
Merged

fix(#464): PR-create hooks resolve PR origin repo, not session ops-fork#465
atlas-apex merged 3 commits into
devfrom
fix/GH-464-pr-hooks-origin-repo

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Fixed cross-repo false-positives in two PreToolUse hooks — when a session is pinned to the ops-fork but gh pr create --repo X targets a sibling repo (e.g. me2resh/apexyard-premium), require-agdr-for-arch-pr.sh was 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.
  • Suppressed the unrelated upstream-fallback in validate-pr-create.sh — the hook was using the ops-fork's upstream remote (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.
  • Added _lib-pr-repo.sh — shared helper that parses --repo from the command, resolves git 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)
  • Existing test_require_agdr_for_arch_pr.sh — 12 pass, 1 pre-existing failure (spike-marker test, exists on dev baseline before this PR)
  • Existing test_validate_pr_create_upstream.sh — 7 pass, 0 fail
  • Existing test_validate_pr_create_head.sh — 8 pass, 0 fail
  • Broader suite (test_single_closes_per_pr.sh, test_detect_role_trigger.sh, test_validate_pr_required_sections.sh) — all pass, no regressions introduced
  • Bash syntax check (bash -n) on all 4 changed/new files

Refs #464

Glossary

Term Definition
ops-fork The apexyard framework repo (this repo) when used as the session-pinned working directory
sibling repo A repo managed under apexyard governance (e.g. me2resh/apexyard-premium) that lives in a separate git clone, not inside the ops-fork
cross-repo false-positive A hook resolving the session-pinned ops-fork instead of the PR's actual origin repo, blocking legitimate sibling-repo PRs
upstream fallback The #207 feature that rechecks a ticket against the upstream git remote when the primary lookup misses — only valid within the same project lineage
_lib-pr-repo.sh New shared library providing pr_repo_matches_cwd — returns 0 when the PR target repo matches the current working tree's origin, 1 otherwise

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>
me2resh and others added 2 commits May 30, 2026 13:51
…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 atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. Framework gating preserved (#1 risk). Traced both hooks: a framework/ops-fork PR (no --repo, or --repo me2resh/apexyard matching origin) → pr_repo_matches_cwd returns 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).
  2. All four --repo forms 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.
  3. No command injection — parsed values flow only into sed/grep/string comparison, never eval or unquoted exec.
  4. C1/C2 sandbox correctly — both copy hook+libs into a temp dir and rm -f only the sandbox copy; the live .claude/hooks/_lib-pr-repo.sh is never mutated (confirmed clean post-run).
  5. Lib-missing = visible WARN, not silent revert — C1 (agdr) and C2 (validate) pass.
  6. #207 fork→upstream fallback preserved — B5 (fork→upstream) and B6 (guard allow-path) pass.
  7. shellcheck clean / CI green — 4/4 checks pass; remaining shellcheck notes are info/style only (pre-existing, CI-tolerated).
  8. The one failing case in test_require_agdr_for_arch_pr.sh (spike active-ticket marker) is confirmed identical on the dev baseline — 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 --body whose 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_cwd then 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 --repo before --body). The four-flag-form coverage is unaffected. Optional hardening: extract --repo only from the pre---body/pre---title command 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

@atlas-apex atlas-apex merged commit c129197 into dev May 30, 2026
4 checks passed
@atlas-apex atlas-apex deleted the fix/GH-464-pr-hooks-origin-repo branch May 30, 2026 15:13
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants