Repository navigation
Conversation
A run polls its Resolution once a second, so one slow build spent the whole guest allowance of 60 requests a minute and failed with RATE_LIMITED. Polls now draw from their own allowance (ADR-0012). GitHub answers a secondary rate limit with 403 or 429 and Retry-After. A 403 failed the run as SOURCE_ACCESS_DENIED, and a 429 threw into the queue retry ladder. Both now fail as RATE_LIMITED with the named wait, and a rate-limited tarball no longer falls back to one read per blob. Every retryable failure now names retryAfterSeconds. The first queue retry waits 5 seconds instead of 60, inside the CLI's 60 second window.
The hourly registry sync spends GITHUB_TOKEN to zero before each GitHub reset. On 2026-10-06, 13 runs failed RATE_LIMITED in those minutes. GitHub counts a personal token's quota per account, so builds now read with ARTIFACT_GITHUB_TOKEN when it is set, and with GITHUB_TOKEN until then.
GitHub counts a personal token's quota per account. GITHUB_TOKEN is a personal token, so the registry sync, page views and the account owner's other tools spend the same bucket that builds need, and runs failed RATE_LIMITED in the minutes before each hourly reset. An App installation has its own bucket. Builds now mint an installation token for the read App and fall back to ARTIFACT_GITHUB_TOKEN, then GITHUB_TOKEN, with a reported reason when the App gives none.
…uota When every shared GitHub bucket is spent, a signed-in run still has its own: the account's GitHub sign-in token reads public Repositories at that account's quota. The build retries only the refused read with it, and only for the account that asked. The requester row lives while the build runs. The build deletes it when it settles, account deletion removes it, and the daily purge removes one a dead build left behind.
GITHUB_TOKEN is a personal token, so the registry shares its hourly quota with every other tool on that account. Measured 2026-10-06: about 1,650 of 1,900 daily sync jobs only advanced the freshness cursor, about 630 /gh views a day re-asked GitHub for a repository it had already said was missing, and the owner and commit reads went out anonymous and failed. The hourly task now reads candidate summaries in GraphQL batches of 100 and records unchanged repositories in place. Sync and page tree reads go by tree SHA. The repository page caches 404s and tree scans at the edge, answers 404 for a private repository, and falls back to ungh.cc metadata when GitHub refuses. Owner and commit reads authenticate through the ETag client, so a repeat read answers 304 for free. Sync pauses name the bucket.
…oken # Conflicts: # layers/artifact-delivery/server/utils/build.ts # layers/artifact-delivery/server/utils/github-source.ts # layers/artifact-delivery/server/utils/queue.ts # test/unit/skill-run-sweep.test.ts
…ies the App neondatabase/agent-skills is public and a personal token reads it, but GitHub answers the read App 403. Every run of its Skills failed SOURCE_ACCESS_DENIED after builds moved to the App. A read the App was denied now repeats once with ARTIFACT_GITHUB_TOKEN or GITHUB_TOKEN, and the denial is reported as an artifact-github-credential event.
…feat/github-app-everywhere
…feat/github-app-everywhere
GITHUB_TOKEN is a personal token, so the registry sync, page views and mention checks spent the same bucket as its owner's tools and CI. Every Worker GitHub read now uses the read App installation token, shared with builds through one isolate cache. GITHUB_TOKEN is read only for a read GitHub denied the App, and each use is reported. Without it, a denied read fails for that Repository only: the sync no longer counts a denial as a spent quota, and a build answers SOURCE_ACCESS_DENIED unless the signed-in requester's token can read it.
1 of 6 tasks
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.
Stacked on #498, #494 and #495. This branch merges all three, so merge them first.
🔗 Linked issue
Related to #491
❓ Type of change
📚 Description
GITHUB_TOKENis a personal token of theharlan-zwaccount. GitHub counts a personal token's quota per account, so the registry sync,/ghpage views and mention checks spent the same bucket as Harlan's own tools, harlan-github-agent and the CI runners. When it ran out, the site and CI stalled together. #491 moved builds to the read App. This moves every other Worker caller./api/repos,/api/orgs, featured and skill-related all go through it. The minting and the token cache move toshared/server/github-app-credential.ts, so the registry and builds share one token per isolate.GITHUB_TOKENbecomes a fallback. It repeats only a read GitHub denied the App: a 403 with quota left and no rate limit message, or aFORBIDDENGraphQL error. Each use emits agithub-credentialevent with outcomeapp-denied. A batch summary leaves a denied alias to the per-repository sync instead of failing the batch.GITHUB_TOKEN. A denied registry read then answersdenied, which the sync records as a failure for that Repository instead of pausing every Repository for a spent quota. A denied build tries the signed-in requester's token from feat(artifact-delivery): fall back to the requester's GitHub token #495, then answersSOURCE_ACCESS_DENIEDwith "GitHub denies the skilld.dev read App access to this Repository, and no fallback token is set."GITHUB_TOKENstays the primary credential.After #491 deployed, production built 150 runs in 41 minutes as the App, across 68 owners. 3 were denied, all
neondatabase/agent-skills, and none wasRATE_LIMITED. Theapp-deniedevents count denials per day once this and #498 deploy.Callers that still need a credential other than the App:
neondatabase. It needsGITHUB_TOKEN,ARTIFACT_GITHUB_TOKENfor builds, or a signed-in requester's own token.GITHUB_TOKENexpiry. Without the secret, it reports nothing.scripts/readGITHUB_TOKENfrom the local environment. They never run in the Worker.📝 Migration
skilld-dev: filteroutcome = app-deniedand group byoperationandgithub.credential.GITHUB_TOKEN, from~/sites/skilld.dev:CLOUDFLARE_ACCOUNT_ID=5904138d55ca25d5670dca6adf99894e pnpm exec wrangler secret delete GITHUB_TOKEN --config wrangler.jsoncCLOUDFLARE_ACCOUNT_ID=5904138d55ca25d5670dca6adf99894e pnpm exec wrangler secret put GITHUB_TOKEN --config wrangler.jsonc--config wrangler.jsoncmatters: a straywrangler.tomlin the checkout otherwise wins.