Repository navigation
Read classic required checks for a token without admin rights - #186
Merged
Merged
Conversation
A token without admin rights got 404 "Not Found" from branches/<b>/protection on every protected branch checked, so its merge rules read as unknown and merging was blocked. The branch read (branches/<b>) showed such a token protection.enabled and protection.required_status_checks (enforcement_level, contexts, checks) on the public repositories checked. Use it on a "Not Found" answer: enabled false with enforcement off and no checks is no classic protection; enabled true takes its checks, held to the protection read's rules. It has no `strict`, so it never gives the atomic base guard. Every other answer still fails closed. Classic protection off whose required-checks object is missing or contradicts itself (enforced, or listing checks) was read as no classic protection and now fails closed too. Recorded with gh 2.102.0 from public repositories read without admin rights: vitejs/vite (ruleset only), cli/cli (classic only) and sveltejs/svelte (both). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Exact-head full-suite and CI validation remain unconfirmed, and write-capable non-admin token behavior is unverified for this merge-safety change.
No open issues 🎉
What changed in this PR
Adds a branch-read fallback for classic required checks when GitHub returns 404 “Not Found” to a non-admin token, while preserving fail-closed merge safeguards.
Changes:
- Validates fallback check lists without granting an atomic base guard.
- Adds recorded responses and regression tests for valid, malformed, and contradictory responses.
- Documents supported observations and unverified token scenarios; app-pinned check handling remains tracked in #176.
| File | Description |
|---|---|
| test/merge.test.ts | Tests fallback validation and base-guard preservation. |
| test/merge-recorded.test.ts | Replays non-admin rule and check responses. |
| test/fixtures/gh/nonadmin-ruleset.json | Records a ruleset-only branch. |
| test/fixtures/gh/nonadmin-mixed.json | Records combined classic and ruleset checks. |
| test/fixtures/gh/nonadmin-classic.json | Records classic required checks. |
| README.md | Clarifies access requirements and fallback limitations. |
| github/merge.ts | Implements validated classic-check fallback. |
| docs/implementation/guarded-merge.md | Documents fallback behavior and evidence limits. |
| docs/designs/codeboost-plan-indexed-review.md | Updates recorded-test evidence. |
| docs/architecture.md | Summarizes non-admin check support. |
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
A token without admin rights got 404 "Not Found" from
branches/<b>/protectionon every protected branch checked. SoGhMergeGateway#rules(github/merge.ts) read its merge rules as unknown and blocked every merge. The branch read (branches/<b>) shows such a tokenprotection.enabledandprotection.required_status_checks(enforcement_level,contexts,checkswithapp_id).On a "Not Found" answer,
#rulesnow uses the branch read:enabled: falsewith enforcementoffand empty lists: no classic protection.enabled: true: the listed checks are required (enforcementoffmeans none). They are held to the protection read's rules: both lists present and naming the same checks, every check with anapp_id.strict, so it never gives the atomic base guard. For such a token, a branch that only classicstrictguards stays blocked bybase-guard.The admin path (protection read answers 200) is unchanged.
Evidence
Recorded with
scripts/record-merge-gh.tsand gh 2.102.0 on 2026-10-20, from public repositories this token may only read:nonadmin-ruleset: vitejs/vite PR 23837 intomain(ruleset only).nonadmin-classic: cli/cli PR 14716 intotrunk(classic checks only).nonadmin-mixed: sveltejs/svelte PR 19050 intomain(both require one check).With main's code, all three read as
rulesKnown: false. With this change they read the correct required checks (test/merge-recorded.test.ts). The vite and svelte checks are bound to an app, so they read asmissingagainst gh's app-less rollup: #176, unchanged and fail-closed.Other shapes seen live without admin rights, used by the hand-written fakes: withastro/astro (
enabled: true,off), biomejs/biome (non_admins, no checks), python/cpython (everyone).Not checked:
Validation (head 12b8cee, base aded16e)
tsc --noEmitclean.strictfrom the branch read fails the base-guard test.Review record
Independent-agent reviews ran in fresh subagents on the full diff against aded16e:
enabled: falsenever checked the branch read's required checks (contradictory answer read as known); overclaims ("every protected branch", "any reader"); a fine-grained token was claimed to get "Not Found"; misleading test title; docstring overclaimoffand empty lists; wording fixed; test renamedstrictread from the branch; misplaced evidence commentstrict: trueadded; mutation confirmedCopilot review
Requested on 12b8cee (base aded16e), authorized by the clean independent round 9 on that pair. Main moved to a56bbdc (#185) after round 9; it touches none of this PR's files, and the PR's merge base is still aded16e.
test(2026-10-20 22:04 UTC) andreal-docker(22:08 UTC) both passed on the exact head 12b8cee.Review lessons audit
enabled: falseanswer (round 1), the missing rows (rounds 2–4, 6) and the 403 row: covered by "Treat every behavioural claim… give it a test that fails if the claim is false" (CLAUDE.md Review readiness) and the Design system and plan format #1 rule.🤖 Generated with Claude Code