fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror #50

Merged
ryangr0 merged 1 commit from fix/github-distribute-fail-fast into main 2026-08-25 05:16:58 +00:00
Owner

Both GitHub-track jobs in webgrip/ploeg run 170 (v0.2.0-rc.20) died from one invalid GH_TOKEN, and only one of them said so.

job symptom
Distribute image (GHCR) docker login ghcr.io -> denied: denied, exit 1
Mirror & Release (GitHub) no failure at all — the push step logged zero lines and sat running for 20+ minutes, holding a runner slot with the caller's flattened job stuck on blocked

Why the messages are so bad

GHCR's token endpoint returns {"code":"DENIED","message":"denied"} for a non-empty token it does not accept, versus the longer "requested access to the resource is denied" for an empty password — so denied: denied reads like a package-permission problem rather than a dead secret. Probed directly against ghcr.io to confirm.

The mirror is worse: github.com 401s the push, git falls back to the credential subsystem, finds no helper, and blocks on a terminal prompt that never arrives. Nothing times out.

Changes

  • Both jobs preflight GH_TOKEN against api.github.com/user before doing any work, and echo the token's scopes. Verified against a junk token: named failure in ~1s instead of the opaque one.
  • The GHCR job additionally hard-fails without write:packages — fine-grained PATs can never carry it, which is the likeliest shape of the bad secret.
  • The push runs with GIT_TERMINAL_PROMPT=0, empty credential.helper / core.askPass, and a 5m timeout backstop, so no auth or egress failure can park a runner again.

This makes the failure legible; it does not make the jobs pass. webgrip/ploeg's GHCR_TOKEN still needs rotating to a classic PAT with repo + write:packages.

Both GitHub-track jobs in `webgrip/ploeg` run 170 (v0.2.0-rc.20) died from one invalid `GH_TOKEN`, and only one of them said so. | job | symptom | |---|---| | `Distribute image (GHCR)` | `docker login ghcr.io` -> `denied: denied`, exit 1 | | `Mirror & Release (GitHub)` | no failure at all — the push step logged zero lines and sat `running` for 20+ minutes, holding a runner slot with the caller's flattened job stuck on `blocked` | **Why the messages are so bad** GHCR's token endpoint returns `{"code":"DENIED","message":"denied"}` for a non-empty token it does not accept, versus the longer `"requested access to the resource is denied"` for an empty password — so `denied: denied` reads like a package-permission problem rather than a dead secret. Probed directly against ghcr.io to confirm. The mirror is worse: github.com 401s the push, git falls back to the credential subsystem, finds no helper, and blocks on a terminal prompt that never arrives. Nothing times out. **Changes** - Both jobs preflight `GH_TOKEN` against `api.github.com/user` before doing any work, and echo the token's scopes. Verified against a junk token: named failure in ~1s instead of the opaque one. - The GHCR job additionally hard-fails without `write:packages` — fine-grained PATs can never carry it, which is the likeliest shape of the bad secret. - The push runs with `GIT_TERMINAL_PROMPT=0`, empty `credential.helper` / `core.askPass`, and a 5m `timeout` backstop, so no auth or egress failure can park a runner again. This makes the failure legible; it does not make the jobs pass. `webgrip/ploeg`'s `GHCR_TOKEN` still needs rotating to a classic PAT with `repo` + `write:packages`.
fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror
Some checks failed
docker-build-and-push-registry.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
docker-build-and-push.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
forgejo-distribute.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
github-distribute.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
github-issue-create-by-prompt.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
github-issues-create-by-prompt.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
helm-chart-deploy.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
helm-chart-push.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
helm-charts-deploy.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
helm-charts-push.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
laravel-quality.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
php-application-static-analysis.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
rust-semantic-release.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
semantic-release-monorepo.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
semantic-release.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
setup-repository-bootstrap.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
setup-repository-copilot-files.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
setup-repository-create-from-template.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
spa-preview.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
static-analysis.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
sync-template-files.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
techdocs-deploy-backstage-s3.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
techdocs-deploy-codeberg.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
techdocs-deploy-docs-site.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
techdocs-deploy-gh-pages.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
tests.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
update_mkdocs.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
update_techdocs.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
wordpress-plugin-release-distribute.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
wordpress-plugin-release.yml / Merge pull request 'fix(github): fail fast on a dead GH_TOKEN instead of hanging the mirror' (#50) from fix/github-distribute-fail-fast into main (pull_request) Failing after 0s
2e2d49eda6
ploeg run 170 lost both GitHub-track jobs to one invalid GH_TOKEN, and only
one of them said so:

- `Distribute image (GHCR)` died at `docker login ghcr.io` with the opaque
  `denied: denied`. GHCR's token endpoint returns exactly that for a token it
  does not accept (an empty password gets the longer "requested access to the
  resource is denied"), so the message reads like a package-permission problem
  rather than a dead secret.
- `Mirror & Release (GitHub)` did not fail at all. github.com 401s the push,
  git falls back to the credential subsystem, finds no helper, and blocks on a
  terminal prompt that never arrives — the job sat `running` for 20+ minutes
  holding a runner slot, with the caller's flattened job stuck on `blocked`.

Both jobs now preflight the token against api.github.com/user before doing any
work, and report the scopes (GHCR additionally hard-fails without
`write:packages`, which fine-grained PATs can never carry). The push runs with
GIT_TERMINAL_PROMPT=0, an empty credential.helper/core.askPass, and a 5m
timeout backstop, so no auth or egress failure can park a runner again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
webgrip-ci referenced this pull request from a commit 2026-08-25 05:17:46 +00:00
Sign in to join this conversation.
No reviewers
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
webgrip/workflows!50
No description provided.