Skip to content

Edge cases

Case Behavior
Base branch is not the default --base wins; otherwise the repository’s default branch is resolved once per run. One instance serves one base branch; a repository with two protected branches runs the tool twice with distinct status contexts.
PR targets another branch (release, feature) Skipped by default: no status is written, and the runbook requires the context only in the base branch’s ruleset so nothing shows “Expected” elsewhere. --other-bases pass writes a not-applicable pass for repositories that require the context everywhere.
Two open PRs share a head SHA Statuses attach to the commit. Two in-scope PRs get the same verdict. With --other-bases pass, an out-of-scope PR sharing a SHA with an in-scope one can overwrite its failure with a pass; keep skip unless you accept that.
Stacked PR retargeted to the base after its parent merges The PR becomes in scope when its base changes; the next refresh-pr-status or refresh stamps it. Until then it carries no status.
Fork PR The head SHA lives in the base repository (refs/pull/N/head), so refresh-pr-status --pr and the refresh evaluate it like any other. Writing a status needs a token with statuses: write, which a fork’s pull_request run does not have; the action documents the pull_request_target path. A public repository has to allow that trigger in an Actions event policy; where it does not, a fork PR is stamped only by a refresh that covers unstamped PRs.
Baseline ref absent That baseline is satisfied everywhere. The refresh stamps unstamped PRs with a pass; move-baseline seeds the ref only with --force.
Baseline ref pointing at a tag object Peeled to the commit it points at, through any chain of tag objects; a cycle is an error.
Baseline not an ancestor of base HEAD (bad manual move, force push) report prints it and exits 2; both refreshes post a pass with a “baseline misconfigured” description, so no author is blocked by an operator mistake, and the sweep fails the step only while some PR still has to learn it. move-baseline reports it and moves nothing until forced. Fix: move-baseline --force (a workflow dispatch with mode move-baseline), which puts the ref back on the base branch.
Concurrent baseline moves The target must descend from the current commit, checked before every write. The write is tried in order as a lease push from the clone, a lease push from a temporary repository, and only then a refs API write (also the path with no git or --ancestry api); the server refuses a lease when the ref moved meanwhile. Through the refs API the ref is re-read after the write, since GitHub enforces no fast-forward outside branches; a forward move landing just before the write is rewound unseen, which is the one case the tool cannot protect. A lost race re-evaluates the decision once from the ref’s new commit: a baseline another writer moved past the target is left there, one moved elsewhere gets the move applied again. A second race ends the run with a retry hint; a rejected write with the ref unmoved is a validation error, which is terminal. Every ref is read once more after the moves, so the result reports what another writer left.
Several path-scoped baselines A PR touching no scope is bound only by unscoped baselines. When the compare API returns 300 files the diff is indeterminate and every scoped baseline binds the PR, which is conservative.
Force push landing inside the refresh’s listing window The scope buckets a PR by the status on its last listed commit while a write goes to its head OID, so a head that changed in between can be bucketed as unstamped and left alone by --scope corrections. The PR’s next event, or one --scope all run, repairs it.
Draft PRs Included by default; includeDrafts: false exists in the library only.
PR closed or pushed during the refresh With the API adapter the status is written to the listed head, which is harmless and converges on the next event. The git adapter lists the open PRs again after fetching their heads and re-reads every PR the two views disagree about: closed is closed, retargeted or newly draft (when drafts are excluded) is out-of-scope, a stable new head is evaluated there, and anything still moving is deferred.
Move-versus-status race refresh-pr-status re-reads the baseline refs before posting; a refresh after the move converges any survivor.
Rate limits Primary, secondary and GraphQL limits all stop the run with the summary and a retry hint; see rate limits.
1,000 statuses on one commit and context GitHub returns a validation error. It is terminal for that commit, counted as failed, never retried. Recovery: push a new commit, or change the status context.
Developers pulling the repository Nothing changes for them: git pull and git fetch ignore refs/baselines/*, so a move never conflicts with a local ref. git ls-remote origin 'refs/baselines/*' shows the refs; git fetch origin '+refs/baselines/*:refs/baselines/*' copies them for offline use.
Consumer repository is a fork running the workflow The action’s consumer workflow carries a fork guard. A 403 on write is reported as a permission error; the permissions page lists what each job needs.
Baseline moved with GITHUB_TOKEN No workflow fires for a ref under refs/baselines/, whatever the token; run the refresh in the same job with --refresh-pr-statuses.
Status written by another creator Rewritten. Identity is state, description, target URL and creator, so a ruleset pinned to a source is always satisfied by the configured token. A write that comes back under a different login than the run resolved is a configuration error and stops the run.
Label removed after merge Not detected. A baseline that should have moved is moved by a forced dispatch.
GitHub Enterprise Server GITHUB_API_URL (https://HOST/api/v3) is honored for REST; GraphQL goes to GITHUB_GRAPHQL_URL, derived as https://HOST/api/graphql when unset.
More than 300 changed files for marker detection through the API Warn and do not auto-move; the git adapter has no cap.