Skip to content

docs(contributing): document the merge gate and the never-arriving-check trap - #941

Merged
blove merged 2 commits into
mainfrom
blove/ci-gate-docs
Sep 1, 2026
Merged

docs(contributing): document the merge gate and the never-arriving-check trap#941
blove merged 2 commits into
mainfrom
blove/ci-gate-docs

Conversation

@blove

@blove blove commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

On 2026-09-01 Vercel consolidated its GitHub commit-status contexts — from one per project (Vercel – threadplane, Vercel – threadplane-minting-service) to a single Vercel. Branch protection required the literal old string, so the required check stopped arriving and every PR became permanently unmergeable while showing all checks green.

It presented as mergeable: MERGEABLE + mergeStateStatus: BLOCKED, which is indistinguishable from a slow queue or a GitHub cache quirk, and never resolves on its own. That cost a lot of confused debugging for what is one API call once you know to make it.

What this adds

A "The merge gate" section in CONTRIBUTING.md:

  • What is required: CI — required (the required-pr-checks job), app-pinned to 15368, strict: true — and that approve (from auto-approve.yml, which exists only to give Scorecard's Code-Review check a review to read) must never join it.
  • Why it's a job we own, not a vendor status string: a required context is matched by name, so any vendor free to rename its context is free to strand every PR indefinitely. A job in this repo can only be renamed in a commit. Also records why CI — required is safe to gate on — it's always()-gated so it posts on fork PRs, and it isn't vacuous.
  • The diagnostic: diff the contexts actually posted on the head SHA against the contexts protection requires, with both gh api calls. Includes the check-runs-vs-statuses API split — CI — required is a check run, so it does not appear in /status output, which is its own trap when eyeballing the two lists.
  • A rollback reference for the pre-change protection config, since branch protection is not version-controlled and needs an admin-scoped token to even read.

On the CI guard

The brief asked me to judge whether a CI assertion that protection matches a committed value is worth it. It isn't feasible without a new credential, and I don't think that trade is worth making:

  • Reading branch protection needs the administration: read scope. The workflow permissions: key has no such scope — GITHUB_TOKEN cannot be granted it — so a guard requires a long-lived admin PAT in Actions secrets.
  • The unprivileged fallback doesn't work here either: this repo uses classic branch protection and has 0 rulesets, so GET /repos/{owner}/{repo}/rules/branches/main returns [] (verified).

An admin PAT's blast radius is worse than the drift it would catch, especially now that the drift is loud once the diagnostic is written down. The reasoning is recorded in the section so the next person doesn't re-derive it.

Verified while writing

  • f9271249["Vercel – threadplane" (success), "Vercel – threadplane-minting-service" (failure)]
  • a8a603f9["Vercel"]
  • CI — required on a8a603f9 → check run, app id 15368
  • current required_status_checksstrict: true, checks: [{context: "CI — required", app_id: 15368}]
  • rulesets0; rules/branches/main[]

Docs only, no behavior change.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Canceled Canceled Sep 1, 2026 11:00pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove force-pushed the blove/ci-gate-docs branch from 9ec6103 to 0f96d2d Compare September 1, 2026 22:38
@blove
blove enabled auto-merge (squash) September 1, 2026 22:38
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

blove and others added 2 commits September 1, 2026 15:48
…eck trap

On 2026-09-01 Vercel consolidated its commit-status contexts from one per
project (`Vercel – threadplane`, `Vercel – threadplane-minting-service`) to a
single `Vercel`. Branch protection required the literal old string, so the
required check stopped arriving and every PR became permanently unmergeable
while showing all checks green — `MERGEABLE` + `BLOCKED`, indistinguishable
from a slow queue. That cost a lot of confused debugging, and the diagnostic is
one API call once you know to make it.

Adds a "The merge gate" section covering:

- what the single required check is (`CI — required`, the `required-pr-checks`
  job, app-pinned to 15368, strict), and that `approve` must never join it;
- why the gate is a job we own rather than a vendor status string a third party
  can rename out from under us;
- the diagnostic — diff the contexts posted on the head SHA against the
  contexts protection requires — including the check-runs/statuses API split
  that makes `CI — required` absent from `/status`;
- the pre-2026-09-01 protection config as a rollback reference, since branch
  protection is not version-controlled and needs an admin token to read.

Deliberately no CI guard asserting protection matches the file: reading branch
protection needs `administration: read`, which the workflow `permissions:` key
cannot grant, so it would require a long-lived admin PAT in Actions secrets.
That credential is a worse risk than the drift it catches. Reasoning is
recorded in the section so the next person doesn't re-derive it.

Verified against the API while writing: f927124 carries the two old contexts
(minting-service failing), a8a603f carries the single `Vercel`, and
`CI — required` is a check run from app 15368.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reviewed 2026-09-01. States the residual gap plainly — a Vercel-environment-
specific failure is not caught pre-merge, only post-merge by `Deploy → Vercel`
— and why requiring the status back was rejected: the consolidated `Vercel`
context now covers every project, so it would let an unrelated project block a
pure-website PR, reversing the #931 behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/ci-gate-docs branch from 0f96d2d to 409a86e Compare September 1, 2026 22:48
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 574a0c0 into main Sep 1, 2026
27 of 28 checks passed
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.

1 participant