Skip to content

ci(auto-update-prs): a denied token was reported as six merge conflicts - #681

Merged
cuttlefisch merged 3 commits into
mainfrom
fix/auto-update-pat-scope-reporting
Aug 7, 2026
Merged

ci(auto-update-prs): a denied token was reported as six merge conflicts#681
cuttlefisch merged 3 commits into
mainfrom
fix/auto-update-pat-scope-reporting

Conversation

@cuttlefisch

Copy link
Copy Markdown
Owner

What happened

The first live run of auto-update-prs.yml after #676's RELEASE_PAT switch (run 31136847506, 2026-08-07) failed to update all six open PRs:

GraphQL: Resource not accessible by personal access token (updatePullRequestBranch)
  #680: update failed (likely a conflict) — author action needed
  ... (x6)
updated: 0   skipped: 0   conflicted: 6

…and the job exited green.

Two defects, same else-branch

1. A 403 is not a conflict. Every non-zero exit was reported as a merge conflict. So a token-scope problem was rendered as six PRs "needing manual merge" — sending six authors after conflicts that do not exist, while the actual cause (a secret missing a permission) appeared nowhere.

2. It exited green. A conflict is per-PR and rightly non-fatal — that is deliberate and documented at the top of the file. A denial is not: it fails every PR identically and stops the workflow doing its job at all. The symptom is an absence — no back-merge, no new CI run — so nothing surfaces it.

#676 added a guard for the token being absent. This is the same silent-green failure one step later: presence was checked, permission was not.

The change

  • Capture gh pr update-branch output and classify on the API's own wording.
  • Count denials separately from conflicts; report both in the log and step summary.
  • exit 1 when any denial occurs, with an error naming the required scope.
  • Conflicts stay non-fatal and per-PR, unchanged.
  • An unrecognised error still falls through to the conflict path, so a new failure mode cannot start failing runs spuriously.

Testing

Classifier exercised against the real error string from the failed run, plus adversarial cases (principle #14 — the negative case is the point):

Input Expected Got
GraphQL: Resource not accessible by personal access token (updatePullRequestBranch) DENIED DENIED
HTTP 403: Forbidden DENIED DENIED
HTTP 401: Bad credentials DENIED DENIED
merge conflict between base and head CONFLICT CONFLICT
GraphQL: Merge conflict (updatePullRequestBranch) CONFLICT CONFLICT
`` (empty/unknown) CONFLICT (non-fatal) CONFLICT

actionlint clean. yamllint -c .yamllint.yml reports only the pre-existing document-start warning, verified present on main's copy too.

This does not fix the workflow

It makes the breakage loud. RELEASE_PAT still needs pull_requests: write (fine-grained) or repo (classic) before auto-update can work at all — a secrets change. Expect this workflow to now fail visibly on the next push to main until that scope is granted. That is the intended outcome: a red run naming the cause beats a green run reporting six phantom conflicts.

🤖 Generated with Claude Code

cuttlefisch and others added 3 commits August 7, 2026 10:17
The first live run of this workflow after the RELEASE_PAT switch (run
31136847506, 2026-08-07) failed to update all six open PRs with:

  GraphQL: Resource not accessible by personal access token
  (updatePullRequestBranch)

and reported the result as `conflicted: 6` — then exited green.

Two defects, both in the same else-branch:

1. A 403 is not a conflict. The branch treated every non-zero exit as a
   merge conflict, so a token-scope problem was rendered as six PRs
   "needing manual merge". That sends six authors to resolve conflicts
   that do not exist, while the single real cause — a secret missing a
   permission — appears nowhere in the output.

2. It exited green. A conflict is per-PR and rightly non-fatal, but a
   denial fails every PR identically and silently stops the whole
   workflow from doing its job. The symptom is an absence (no back-merge,
   no new CI run), so nothing surfaces it. #676 added a guard for the
   token being *absent* and this is the same silent-green failure one
   step later: presence was checked, permission was not.

Classify the failure on the API's own wording, count denials separately,
and exit non-zero when any occur. Conflicts stay non-fatal and per-PR, as
documented at the top of the file. An unrecognised error still falls
through to the conflict path — non-fatal — so a new failure mode cannot
start failing the run spuriously.

Note: this makes the misconfiguration visible; it does not repair it.
RELEASE_PAT still needs 'pull_requests: write' (fine-grained) or 'repo'
(classic) before auto-update can work at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cuttlefisch
cuttlefisch merged commit ad4856e into main Aug 7, 2026
23 checks passed
@cuttlefisch
cuttlefisch deleted the fix/auto-update-pat-scope-reporting branch August 7, 2026 10:27
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