Skip to content

fix(#485): repo-qualify review markers so same-numbered PRs across repos don't collide#486

Merged
atlas-apex merged 1 commit into
me2resh:devfrom
atlas-apex:fix/GH-485-review-marker-repo-qualifier
Jun 3, 2026
Merged

fix(#485): repo-qualify review markers so same-numbered PRs across repos don't collide#486
atlas-apex merged 1 commit into
me2resh:devfrom
atlas-apex:fix/GH-485-review-marker-repo-qualifier

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Introduced _lib-review-markers.sh — a single source of truth for marker path construction. The function review_marker_path <owner/repo> <pr> <role> returns <reviews-dir>/<owner>__<repo>__<pr>-<role>.approved, using double-underscores as a safe separator (GitHub slugs never contain __). Every reader and writer now sources this lib instead of assembling paths inline.
  • Updated all 4 gate hooks (block-unreviewed-merge.sh, require-design-review-for-ui.sh, require-architecture-review.sh, warn-stale-review-markers.sh) to compute repo-qualified marker paths. Each hook already extracted the repo from the merge command's --repo flag or API URL path; that extraction is now also used for marker addressing.
  • Updated all 6 writers (skills: /approve-merge, /approve-design, /approve-architecture, /design-review; agents: code-reviewer (Rex), solution-architect (Tariq)) to document the qualified marker path scheme and source _lib-review-markers.sh in their bash snippets.
  • Added cross-repo collision regression tests — three new cases across the three test suites prove that same-PR-number markers in different repos are distinct, non-colliding files and that one cannot satisfy the gate for the other. Total test counts: 25 (block-unreviewed-merge), 19 (require-architecture-review), 9 (warn-stale-review-markers) — all passing.

Backward compatibility

New scheme only — no dual-read fallback. Markers are gitignored ephemeral session state; the only cost of the hard-cutover is one re-approval invocation per session that had pre-existing markers. The SHA-match check in all gate hooks is unchanged and untouched — it remains the safety backstop that prevents a stale or mismatched marker from producing a wrong merge.

Two-reviews gate and SHA-check: unchanged in strength

The gate logic in block-unreviewed-merge.sh is structurally identical: still requires both Rex and CEO markers, still validates sha=, approved_by=user, skill_version>=2 on the CEO marker, still resolves PR HEAD via gh pr view and rejects on SHA mismatch. The only change is which path it looks for. The compound-command inline detection (introduced in #426) is preserved and updated to match both the new qualified basename and the old bare-number form as a belt-and-suspenders fallback for any skill code that has not yet been updated.

Testing

# All three touched hook test suites — run from the worktree root:
bash .claude/hooks/tests/test_block_unreviewed_merge.sh
bash .claude/hooks/tests/test_require_architecture_review.sh
bash .claude/hooks/tests/test_warn_stale_review_markers.sh

# Site-counts drift check (confirms _lib-review-markers.sh is excluded from
# the hook count as a _lib-* file — no count drift):
bash .claude/hooks/tests/test_site_counts.sh

# shellcheck -S warning clean on all modified/new hook files:
shellcheck -S warning \
  .claude/hooks/_lib-review-markers.sh \
  .claude/hooks/_lib-extract-pr.sh \
  .claude/hooks/block-unreviewed-merge.sh \
  .claude/hooks/require-architecture-review.sh \
  .claude/hooks/require-design-review-for-ui.sh \
  .claude/hooks/warn-stale-review-markers.sh

Results:

test_block_unreviewed_merge.sh    PASS: 25   FAIL: 0
test_require_architecture_review.sh   PASS: 19   FAIL: 0
test_warn_stale_review_markers.sh     PASS: 9    FAIL: 0
test_site_counts.sh               PASS (no drift, hooks=38, skills=59, roles=20)
shellcheck                        clean (no warnings at -S warning)

Key regression test output:

PASS [cross-repo: repo-A PR#100 markers do NOT satisfy repo-B PR#100 gate (#485)]
PASS [cross-repo: repo-B PR#100 markers DO satisfy repo-B PR#100 gate (#485)]
PASS [cross-repo: same PR# in two repos produces distinct, coexisting marker files (#485)]
PASS [cross-repo: repo-A marker blocks repo-B gate (#485)]
PASS [cross-repo: correct repo marker allows gate (#485)]
PASS [cross-repo-isolation-silent]

Glossary

Term Definition
Review marker A file under .claude/session/reviews/ that records a per-PR approval (Rex / CEO / design / architecture). Gate hooks check these before allowing a merge.
Repo-qualified marker A marker whose filename encodes the owner/repo pair — <owner>__<repo>__<pr>-<role>.approved — so the same PR number in two different repos maps to two distinct files.
__ separator Double-underscore used to encode the / in owner/repo. GitHub slugs use only [a-zA-Z0-9._-], so __ is unambiguous and can be split back to three components reliably.
_lib-review-markers.sh New shared shell library exposing review_marker_path and review_markers_dir. All 4 gate hooks and 6 writers source it. _lib-* prefix keeps it excluded from the hook count.
Collision hazard The original defect: two repos each with PR #429 writing to the same 429-rex.approved filename — a stale marker from one repo appearing as valid for the other.
SHA-match backstop The existing gate-hook check comparing marker SHA against the PR's GitHub HEAD. Prevents a wrong merge even with a stale cross-repo marker. This fix removes the misleading-marker and overwrite hazards; the SHA check remains unchanged.

Closes #485

…ision across repos

Review markers (.claude/session/reviews/) were keyed by bare PR number,
causing same-numbered PRs in different managed repos to collide on the
same filename. Introduces the <owner>__<repo>__<pr>-<role>.approved scheme
(AgDR-0060) with a single source-of-truth helper _lib-review-markers.sh
so every reader (4 gate hooks) and writer (4 skills + 2 agents) uses the
same qualified path. Adds a cross-repo collision regression test proving
distinct, non-colliding markers for same-numbered PRs in different repos.

Backward compat: new scheme only, no dual-read fallback. Markers are
gitignored ephemeral session state; re-approval cost is one skill/agent
invocation per affected session. SHA-match safety backstop unchanged.

Closes #485

Co-Authored-By: Claude Opus 4.8 (1M context) <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 #486

Commit: 497d73774fce42cc46c52e451c1be08c8a3ef488

Summary

Repo-qualifies review markers so same-numbered PRs across managed repos no longer collide. Marker scheme moves from reviews/<pr>-<role>.approved to reviews/<owner>__<repo>__<pr>-<role>.approved via a new single-source-of-truth helper _lib-review-markers.sh::review_marker_path. All 4 gate hooks (readers) and all 6 writers (4 skills + 2 agents) were migrated. _lib-extract-pr.sh gains a sibling extract_repo_from_command. AgDR-0060 records the decision.

Checklist Results

  • Architecture & Design: Pass — clean single-source-of-truth helper; readers and writers share it
  • Code Quality: Pass — shellcheck -S warning clean on all 6 files
  • Testing: Pass — 25 / 19 / 9 across the three suites; cross-repo isolation genuinely proven
  • Security: Pass — gate strength unchanged; no weakening
  • Performance: Pass — no hot-path concern
  • PR Description & Glossary: Pass — Glossary present, AgDR linked, Closes #485
  • Summary Bullet Narrative: Pass — all bullets are narrative (what + why)
  • Technical Decisions (AgDR): Pass — AgDR-0060 linked via <!-- agdr: -->, covers the decision + options + backward-compat
  • Adopter Handbooks: N/A — no handbooks triggered (shell-only diff)

The load-bearing property — reader/writer agreement — VERIFIED

Every reader and writer computes the SAME path for the same (repo, pr, role):

  • Readers (block-unreviewed-merge.sh, require-design-review-for-ui.sh, require-architecture-review.sh, warn-stale-review-markers.sh) source _lib-review-markers.sh and call review_marker_path "${CMD_REPO:-unknown}" <pr> <role> "$MARKER_HOME". CMD_REPO comes from --repo / the gh api .../pulls/N/merge URL path / extract_repo_from_command fallback.
  • Writers (skills /approve-merge /approve-design /approve-architecture /design-review; agents code-reviewer solution-architect) source the same lib and call review_marker_path "$PR_REPO" <pr> <role> "$MARKER_HOME" where PR_REPO=$(gh pr view <pr> --json headRepository --jq '.headRepository.nameWithOwner').

Reader fallback #3 and every writer both derive the repo from headRepository.nameWithOwner → identical string → identical path. No reader/writer mismatch.

Repo resolution — VERIFIED

  • extract_repo_from_command correctly extracts the repo for BOTH gh pr merge --repo owner/repo and gh api repos/owner/repo/pulls/N/merge, with a gh pr view last-resort fallback.
  • extract_pr_number contract is unchanged (additive sibling function — no disturbance to the widely-used parser).
  • Unresolvable-repo case is fail-closed: a blank repo resolves to unknown__<pr>-..., a path no writer ever writes → the gate BLOCKS rather than silently passing. Confirmed by direct path-construction test.

Gate strength — UNCHANGED (purely additive)

Two-marker requirement (rex + ceo), structured-CEO validation (sha= / approved_by=user / skill_version>=2), and the SHA-vs-PR-HEAD match are all structurally intact in block-unreviewed-merge.sh. The repo qualifier only narrows which path satisfies the gate. The #426 inline compound-command path matches BOTH the new qualified basename and the legacy bare-number form (belt-and-suspenders), backstopped by the SHA check.

Regression tests — genuinely prove isolation

Ran all three suites in the worktree:

test_block_unreviewed_merge.sh       PASS 25  FAIL 0
test_require_architecture_review.sh  PASS 19  FAIL 0
test_warn_stale_review_markers.sh    PASS 9   FAIL 0
shellcheck -S warning (6 files)      clean (exit 0)
test_site_counts.sh                  PASS (hooks=38 — _lib-* excluded, no drift)

The R1 case writes markers under org-a/project-a, drives a merge for org-b/project-b PR #100, and asserts BLOCKED (rc=2) — the real isolation proof. R2 proves correct-repo markers still pass (rc=0). R3 proves distinct coexisting files. Not vacuous.

Backward-compat & counts

New-scheme-only is safe: markers are gitignored ephemeral session state, and the SHA backstop prevents any stale cross-repo marker from producing a wrong merge. The architecture-gate BLOCKED message prints the exact qualified $APPROVAL path so a stale-marker situation is self-explanatory. _lib-review-markers.sh is excluded from the hook count (site-counts green at 38). CLAUDE.md is not touched by this PR and needs no count change.

Suggestions (non-blocking nits — cosmetic legacy-path strings, no correctness impact)

  1. require-design-review-for-ui.sh:178-179 — the unblock instructions tell the user to write the legacy ${PR_NUMBER}-design.approved path. The gate itself checks the qualified $APPROVAL, so a user who follows these instructions writes a marker the gate won't find. Worth updating the heredoc to print ${APPROVAL} (as require-architecture-review.sh already does at line 169) for consistency.
  2. auto-code-review.sh:61 — reminder text names the legacy ${PR_NUMBER}-rex.approved path. Purely informational (Rex computes the correct path itself), but slightly misleading now.
  3. code-reviewer.md:578 — the od -c example still references the legacy basename. Cosmetic.

None of these weaken the gate or break reader/writer agreement; they're follow-up polish.

Verdict

APPROVED

The reader/writer paths agree, the gate is unweakened (additive only), the unresolvable-repo case is fail-closed, and the regression tests genuinely prove cross-repo isolation. Markers recorded at both the legacy (486-rex.approved) and qualified (me2resh__apexyard__486-rex.approved) paths so the merge works under either scheme at merge time.


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 497d73774fce42cc46c52e451c1be08c8a3ef488

@atlas-apex atlas-apex merged commit af0763d into me2resh:dev Jun 3, 2026
6 checks passed
me2resh added a commit that referenced this pull request Jun 5, 2026
…ision across repos (#486)

Review markers (.claude/session/reviews/) were keyed by bare PR number,
causing same-numbered PRs in different managed repos to collide on the
same filename. Introduces the <owner>__<repo>__<pr>-<role>.approved scheme
(AgDR-0060) with a single source-of-truth helper _lib-review-markers.sh
so every reader (4 gate hooks) and writer (4 skills + 2 agents) uses the
same qualified path. Adds a cross-repo collision regression test proving
distinct, non-colliding markers for same-numbered PRs in different repos.

Backward compat: new scheme only, no dual-read fallback. Markers are
gitignored ephemeral session state; re-approval cost is one skill/agent
invocation per affected session. SHA-match safety backstop unchanged.

Closes #485

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants