fix: authenticate camoufox fetch with GITHUB_TOKEN - #309
Draft
l2ysho wants to merge 1 commit into
Draft
Conversation
`camoufox fetch` lists releases from api.github.com. Unauthenticated, every leg of the image matrix shares one runner IP's 60 requests/hour budget, so a leg occasionally loses the race and gets a 403. On the Python side that failure is silent: the sync swallows the error, `camoufox fetch` still exits 0, and the image ships with no browser. It then dies one step later in "Test image" with CamoufoxNotInstalled. Seen in run 34317591187, where 24 of the 25 camoufox legs on the same SHA passed. Pass GITHUB_TOKEN into both camoufox builds as a BuildKit secret, which lifts the budget to 1000 requests/hour per repository. A secret rather than a build arg keeps the token out of the image layers and out of `docker history`. Builds that pass no secret stay unauthenticated and still work. mode=0444 on the node mount because that stage runs as myuser and secrets default to root-owned 0400. Also assert on the Python side that the browser really landed, so any future fetch failure is a red build naming the cause instead of a green build shipping a broken image. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📦 Image size reportBuilt images compared against the currently published rolling tag for the same runtime version (e.g. Python + Playwright images — 25 images (at 5d99792)
Node + Playwright images — 30 images (at 5d99792)
|
vladfrangu
reviewed
Sep 9, 2026
| \ | ||
| # Install Camoufox browser | ||
| && PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 npx camoufox-js fetch \ | ||
| && GITHUB_TOKEN="$(cat /run/secrets/github_token 2>/dev/null || true)" \ |
Member
There was a problem hiding this comment.
I feel like we should unset the env variable if the file is not present
Comment on lines
+139
to
+142
| # `camoufox-js fetch` reads api.github.com, where the whole matrix shares one 60/hour budget. | ||
| # GITHUB_TOKEN lifts it to 1000/hour; a secret, not a build arg, keeps it out of the layers. | ||
| # mode=0444 because this stage runs as myuser and secrets default to root-owned 0400. | ||
| # Without it the build stays unauthenticated and still works. |
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.
Note
I was testing my skill for flake job detection, I found this one and fixed it with
/fix-flaky-test. Seems legit, feel free to take a look, cancel or whatever. cc @vladfranguProblem
camoufox fetchlists releases fromapi.github.com. Unauthenticated, every leg of the image matrix shares one runner IP's 60 requests/hour budget, so a leg occasionally loses the race and gets a 403.On the Python side that failure is silent.
_do_synccatches the per-repo exception,camoufox fetchreturns without an error code, and the image ships with no browser. It then dies one step later inTest image:Seen in run 34317591187 —
py 3.14 / pw 1.60.0red while 24 of the 25 camoufox legs on the same SHA passed. Nothing about that combination relates to a GitHub API quota, so it reads like a platform break but is not one.Change
GITHUB_TOKENinto both camoufox builds as a BuildKit secret, lifting the budget to 1000 requests/hour per repository. A secret rather than a build arg keeps the token out of the image layers and out ofdocker history. Builds that pass no secret (local, forks) stay unauthenticated and still work.mode=0444on the node mount only — that stage runs asmyuser, and secrets default to root-owned0400. The Python stage runs as root, so its default is correct.camoufox-jsthrows on a failed fetch, so the node image was never silently broken — it gets the token but needs no guard.Verification
The original 403 can't be reproduced on demand, so the same code path was driven with a deliberately invalid token:
401 Client Error: Unauthorized→ guard fails the build. Proves the secret reaches camoufox's request headers.Synced 30 versions from 3 repos.→Camoufox installed: 152.0.4-beta.30→ greendocker runimage testAll browser tests passed.mode=0444removed (control)hadolint and actionlint both match baseline exactly (zero new findings). Token absent from
docker history.Notes for review
RUNinstructions invalidate the GHA layer cache once; the first run after merge rebuilds the camoufox layer on every leg.camoufox.pkgman.installed_verstr, the same function the runtime calls on launch (also present in 0.4.11, so older matrix legs are fine). An upstream rename would fail the build loudly rather than silently.pull_requestbuild steps too, so a PR editing the Dockerfile could read/run/secrets/github_tokenat build time. Fork PRs get a read-only token by design and same-repo branches already imply write access.build-mainhas no explicitpermissions:block; scoping one down would be a separate change.make test-*-camoufoxtargets can't pass a token, so heavy local iteration still shares the 60/hour budget. Left alone deliberately — wiring an optional secret through four Makefile targets needs$(COMMA)escaping and full image builds to validate.🤖 Generated with Claude Code