Skip to content

[Bug] Merge-gate hooks read local HEAD instead of PR HEAD — forces manual 'gh pr checkout' before every merge #55

@atlas-apex

Description

@atlas-apex

Problem

The three merge-gate hooks (`block-unreviewed-merge.sh`, `require-design-review-for-ui.sh`, `block-merge-on-red-ci.sh`) validate approval markers by comparing the marker SHA to `$(git rev-parse HEAD)` — i.e. the local HEAD. But `gh pr merge ` merges the PR's branch on GitHub, which is almost certainly NOT the local HEAD (local is usually `main` or a different feature branch).

Result: every `gh pr merge N` is rejected with:

```
BLOCKED: Code-reviewer approved commit but HEAD is now .
```

Workaround applied in every merge: `gh pr checkout N && gh pr merge N`. Tedious, error-prone, and creates extra local branches that have to be cleaned up. Hit on every merge during the 2026-04-14 merge train (6 PRs).

Proposed fix

In each merge-gate hook, read the PR's real HEAD from the command itself, not from local git state:

```bash

Extract PR number from the command (already done via _lib-extract-pr.sh)

PR_NUMBER=$(extract_pr_number "$COMMAND")

Read the PR's actual HEAD via gh — NOT via git rev-parse HEAD

PR_HEAD=$(gh pr view "$PR_NUMBER" --json headRefOid --jq .headRefOid 2>/dev/null)

Compare marker against PR_HEAD, not local HEAD

if [ "$(cat "$MARKER")" != "$PR_HEAD" ]; then

stale approval

fi
```

Needs the same change in all three hooks. Share the `PR_HEAD` resolution via `_lib-extract-pr.sh` (already exists after #47) for consistency.

Cross-repo caveat

`gh pr view` without `--repo` reads the current repo. For `gh api repos///pulls//merge` shape, the owner/repo is already extracted by `_lib-extract-pr.sh` — pass it to `gh pr view --repo <owner/repo> ` so cross-repo merges work (e.g. contributors from a fork).

Acceptance Criteria

  • All three hooks read PR HEAD via `gh pr view`, not `git rev-parse HEAD`
  • Local state (current branch, uncommitted changes) no longer affects merge-gate decisions
  • `gh pr merge N` works from any branch — no more forced `gh pr checkout` dance
  • Cross-repo shape (`gh api repos/.../pulls//merge`) also reads the target repo's PR HEAD
  • Manual test: merge a PR from `main`, from an unrelated feature branch, and from a detached HEAD — all three should work identically

Priority

P2 — cosmetic papercut, not a security hole. The hook is still correct in its intent (block stale approvals); it's just reading the wrong SHA source. Workaround is one extra command.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions