Skip to content

Authenticate the latest-release lookup - #4

Merged
nicodes merged 2 commits into
mainfrom
fix/authenticate-latest-release-lookup
Aug 1, 2026
Merged

Authenticate the latest-release lookup#4
nicodes merged 2 commits into
mainfrom
fix/authenticate-latest-release-lookup

Conversation

@nicodes

@nicodes nicodes commented Aug 1, 2026

Copy link
Copy Markdown
Member

scripts/install_cli.sh resolves latest with an unauthenticated GitHub API call:

latest_tag() {
  curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \

That is capped at 60 requests an hour per IP address. CI runners share addresses — GitHub-hosted macOS runners heavily so — and the result is:

curl: (56) The requested URL returned error: 403
failed to resolve latest release for aviorstudio/gdam

Why this matters now

It fails intermittently for every repository using gdam-actions/install with the default version: latest — 16 of them. It blocked the gdam-actions release three times in a row, with successes interleaved, which is what made it read as flake rather than as a rate limit.

It also predates this work: there's a matching install (macos-latest) failure in gdam-actions CI history from before any of it.

The change

Send a token when one is available. GDAM_GITHUB_TOKEN first, so a caller can point this at a different token than whatever GITHUB_TOKEN holds; then GITHUB_TOKEN and GH_TOKEN, which CI and the gh CLI already set.

What that buys, stated accurately: 1,000/hour per repository for a workflow's GITHUB_TOKEN (5,000/hour for a personal access token). The denominator is the real fix — one repository's runs stop competing with every other repository that happens to share a runner address. Each workflow run makes exactly one latest lookup, so the headroom is ~1000×.

The unauthenticated path is untouched. Installing by hand from your own address is nowhere near 60/hour, so no token is required and nothing about that case changes.

The token goes to curl via a config on stdin, not -H on the command line — arguments are visible in the process list to every other user on the machine, and this script runs on shared boxes as well as in CI.

The error message now names the cause. It previously said only that the release could not be resolved, which reads as "there is no release" and sends you to the wrong repository. The limit was the actual cause every time it fired.

--retry 3 covers the transient half. 429 and 5xx are retried; a 403 from the rate limiter is not, so an exhausted quota still fails fast rather than sleeping through three attempts.

Verified

All four paths, end to end against the real API:

with a token installs gdam 0.0.7
without a token installs gdam 0.0.7 — unchanged
pinned VERSION=0.0.6 installs gdam 0.0.6, never touches the API
lookup fails prints the new rate-limit guidance

And that the header is genuinely applied, rather than both paths merely happening to succeed:

with token:    limit=5000     # a PAT; a workflow GITHUB_TOKEN reports 1000
without token: limit=60

sh -n and shellcheck -s sh clean. (One pre-existing SC2016 at line 176 — the literal $HOME in the "rerun with INSTALL_DIR=…" advice — is correct as-is and untouched.)

Needed alongside this

This makes the script able to authenticate; it doesn't make CI pass on its own. GITHUB_TOKEN is not in the environment by default in Actions. gdam-actions/install will take a token input defaulting to ${{ github.token }}, so consumers get it for free without changing anything.

nicodes and others added 2 commits August 1, 2026 02:35
Resolving "latest" calls the GitHub API unauthenticated, and that is
capped at 60 requests an hour PER IP ADDRESS. CI runners share addresses,
GitHub-hosted macOS runners heavily so, and the result is a 403 several
times an hour:

    curl: (56) The requested URL returned error: 403
    failed to resolve latest release for aviorstudio/gdam

That failed installs intermittently for every repository using
gdam-actions, and it blocked a release three times in a row this morning
before the cause was clear -- which is the second half of this change.

The token is optional and the unauthenticated path is untouched: someone
installing by hand from their own address is nowhere near the limit.
GDAM_GITHUB_TOKEN is checked first so a caller can point this at a
different token, then GITHUB_TOKEN and GH_TOKEN, which CI and the gh CLI
already set.

It reaches curl through a config on STDIN rather than as -H on the
command line, because arguments are visible in the process list to every
other user on the machine and this script runs on shared boxes as well as
in CI.

The error message now names the rate limit when no token was used. It
previously said only that the release could not be resolved, which reads
as "there is no release" and sends you to the wrong repository -- the
limit was the actual cause every time it fired.

--retry covers the transient half: 429 and 5xx are retried, and a 403
from the rate limiter is not, so an exhausted quota still fails fast
rather than sleeping through three attempts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5000/hour is a personal access token. A workflow's GITHUB_TOKEN gets
1000/hour PER REPOSITORY, which is the number that matters here because
CI is where this fires.

Both are a large improvement on 60/hour shared across every repository on
a runner address, and the denominator is the real change -- but the code
and the error message should not state a figure nobody will observe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nicodes
nicodes merged commit c2b04a1 into main Aug 1, 2026
1 check passed
@nicodes
nicodes deleted the fix/authenticate-latest-release-lookup branch August 1, 2026 08:42
nicodes added a commit to aviorstudio/gdam-actions that referenced this pull request Aug 1, 2026
The install script can now use a token for the API call that resolves
"latest" (aviorstudio/gdam#4). It is not in the environment by default in
Actions, so it has to be handed over -- this does that, defaulting to the
workflow's own token so no consumer has to change anything.

The unauthenticated API allows 60 requests an hour PER IP ADDRESS, and
runners share addresses. macOS runners share them heavily enough that the
lookup returned 403 several times an hour, which failed installs
intermittently across all sixteen repositories using this action and
blocked this repo's own release three times. Authenticated, the limit is
scoped to the token: 1000/hour per repository, against one lookup per run.

Passed as GDAM_GITHUB_TOKEN rather than GITHUB_TOKEN so the script gets
exactly what the caller chose, instead of quietly inheriting whatever the
job happened to have in GITHUB_TOKEN.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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