Skip to content

feat(artifact-delivery): fall back to the requester's GitHub token - #495

Merged
harlan-zw merged 5 commits into
mainfrom
feat/run-own-github-token
Oct 6, 2026
Merged

harlan-zw merged 5 commits into
mainfrom
feat/run-own-github-token

Conversation

@harlan-zw

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #491

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Every public build reads GitHub with a shared credential: the read App installation from #491, then ARTIFACT_GITHUB_TOKEN, then GITHUB_TOKEN. When those buckets are spent, every run fails RATE_LIMITED, signed in or not. A signed-in account already stores its GitHub sign-in token, and GitHub gives each account its own 5,000 requests an hour.

A public build now repeats a read the shared quota refused, with the token of the account that asked for that Resolution.

  • POST /api/v1/resolutions records the signed-in account, from the session or a CLI token, in artifact_resolution_requesters. An anonymous request records nothing and still fails RATE_LIMITED.
  • A build reads only the token of its own Resolution's requester, so one account's token never reads another account's build. Every answer other than RATE_LIMITED stays the shared one.
  • The token's scopes are read:user and user:email, which read no private Repository. The public client also refuses a Repository that GitHub reports as private. So an Artifact built with a requester's token holds public content, and later runs of that commit can reuse it.
  • The requester row is personal data with no use after the build. The build deletes it when it settles, account deletion deletes it, and the daily purge removes one that a dead build left behind after a day.
  • The Resolution answer gains no field, because CLI 3.6.1 parses it with deny_unknown_fields. The CLI can suggest skilld auth login for a RATE_LIMITED run that was not signed in.

/gh page views keep the shared token. Their GitHub reads run inside defineCachedEventHandler, which passes the handler only its varies headers, so the handler never sees the viewer's cookie.

Architecture: the requester fallback from the resolution route through the build to GitHub, with the requester table and its cleanup paths

🤖 AI disclosure: Harlan Agent Kit modified this description. My AI open-source policy.

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.
…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
@harlan-zw harlan-zw added the harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. label Oct 6, 2026
@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. and removed harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. labels Oct 6, 2026
@harlan-github-agent

harlan-github-agent Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

🤖 MERGED

Harlan Agent Kit posted this automated review. It is not Harlan's personal review or approval. AI open source policy. Last updated: 2026-10-06 16:37 UTC.

GitHub merged this pull request.

  • Logged (20/100): The one image in the pull request description is inaccessible, so the documented requester-token data flow was verified only against the code, never against the author's diagram.
    • Ask an agent to verify and fix this finding
  • Logged (30/100): A failure between creating the Resolution and recording its requester leaves the fallback silently lost for that Resolution's lifetime. View code
    • Ask an agent to verify and fix this finding
  • Logged (30/100): The expiry path gives up silently, so an inert fallback is indistinguishable from a working one in operations. View code
    • Ask an agent to verify and fix this finding
  • Logged (28/100): A transient failure in the requester's own read turns a clean rate-limit failure into a retry-ladder outage classification. View code
    • Ask an agent to verify and fix this finding

698742d6-872f-43ca-9abd-8f522f744558

Selected findings run after merge and open separate pull requests.

@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-ready The automated Review passed every gate on this head commit. and removed harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. labels Oct 6, 2026

@harlan-github-agent harlan-github-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harlan Agent Kit posted this automated review. AI open source policy.

Automated Review: READY.

Read the Review results

@harlan-zw
harlan-zw merged commit c2dae4b into main Oct 6, 2026
7 checks passed
@harlan-github-agent harlan-github-agent Bot removed the harlan-agent-ready The automated Review passed every gate on this head commit. label Oct 6, 2026
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