Skip to content

Commit convention check fails on merge commits, so GitHub's own "Update branch" button reds a conforming PR #37

Description

@hyperpolymath

What happens

Repo hygiene → Commit convention check fails whenever a PR's head commit
is a merge commit — which is exactly what GitHub's own Update branch
button produces:

##[error]commit subject fails the conventional pattern: Merge branch 'main' into fix/gate-triage-no-gate
##[error]Process completed with exit code 1.

Observed on run
35642219890
(PR #36, head 5b99414a). Every commit the contributor actually wrote on that
branch was conventional; the only non-conforming subject was the one GitHub
generated.

Why

.github/workflows/ci.yml, the Commit convention check step:

env:
  HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
  subject="$(git log -1 --pretty=%s "$HEAD_SHA")"

The step is already careful — it deliberately reads pull_request.head.sha
rather than HEAD, precisely to avoid grading the ephemeral merge-preview
commit. That is the right instinct and it is not the bug.

The bug is that it grades exactly one commit, and assumes that commit is
authored by the contributor. When the branch is updated from main through the
GitHub UI, the branch tip becomes a real merge commit, so head.sha now
points at a subject nobody typed and nobody can rewrite without force-pushing.

So the gate asks "is the tip conventional?" when what it means is "are the
commits this PR proposes conventional?" — a guard asking a different question
than its consumer.

Why it matters

continue-on-error on this step is
${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }}, so it is
soft upstream and hard here. The upstream PR is unaffected; our own PRs go
red. The practical effect is that Update branch — the one-click remedy GitHub
offers for a stale branch — makes a conforming PR fail, and the only escape is
a force-push that discards the merge. That trains contributors to route around
the gate rather than satisfy it.

It is also a false negative risk, not merely noise: the check grades one
commit, so a branch of ten commits where only the tip is conventional passes
today. Widening it to the PR's actual commit range fixes both directions at
once.

Acceptance criteria

  1. The check grades every commit the PR proposes —
    git log --no-merges <base>..<head> — not just the tip.
  2. Merge commits are excluded (--no-merges, i.e. any commit with ≥2
    parents). A PR whose head is a merge commit but whose own commits all
    conform passes.
  3. A PR containing a non-conventional non-merge commit still fails, and
    the error names which commit, not just the subject.
  4. The fetch depth is sufficient for the range to resolve — actions/checkout
    defaults to fetch-depth: 1, so a naive base..head will fail to resolve
    rather than fail the gate. Whatever depth or git fetch is needed must be
    explicit, and an unresolvable range must fail loudly, never silently
    grade zero commits.
  5. A control proving criterion 4: point the check at a range it cannot resolve
    and confirm it errors rather than printing a pass. A gate that grades zero
    commits must not report success
    — an empty range is the vacuous-pass
    pattern, and this repo's own hygiene gate exists to prevent exactly that.

Not a blocker

Filed per the standing rule that a new finding becomes an issue with
acceptance criteria rather than a merge blocker. PR #36 has already merged;
this is the gate's behaviour, not that change's defect.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions