Skip to content

fix(github): keep each App mint inside the request that started it - #540

Merged
harlan-zw merged 4 commits into
mainfrom
fix/mint-request-scope
Oct 7, 2026
Merged

harlan-zw merged 4 commits into
mainfrom
fix/mint-request-scope

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #525

❓ Type of change

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

📚 Description

The installation token cache from #525 kept the mint in flight in the isolate. A request that found no token awaited the mint another request had started. workerd ties a fetch to the request that made it. If that request answers before its mint finishes, workerd drops the mint, and the shared promise never settles. Its cache entry never cleared either. Every later request in the isolate that found no token then waited on it: until its deadline, or forever without one. opennextjs-aws#1218 and workers-sdk#12731 hit the same category.

What changes:

  • InstallationTokenCache holds finished values only: a token with its expiry, and the failure window. Its type has no field for a promise.
  • The mint in flight lives in the GithubCredential. Callers already create one per request, task, or queue batch through resolveGithubBindings and createArtifactBuildDependencies. Reads in one request still share one mint.
  • Two requests that find no token each mint their own. A cold burst costs a few extra mints.
  • If another request records a failure while this request's mint runs, this mint's failure emits no second event. The window keeps one event.
  • The 401 re-mint, the 5 minute early renewal, the failure window, and rate limit handling are unchanged.

One test changes on purpose: three requests that meet a failing App now make three mints, and still emit one report.

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

A mint ignored the 4 second page read limit, so a hung GitHub held /gh
pages for the mint's own 15 seconds on each read. A failed mint was not
remembered, so every read tried a new one and reported it, and the sync
paced on the fallback token the whole time.

A read now passes its deadline to the mint it waits for. A failed mint
stands for about 60 seconds per isolate with one event. A deadline that
ends a mint is no failure, and a 401 on a minted token still re-mints
once inside a window.
The isolate token cache shared one mint promise across requests. workerd
ties a fetch to the request that made it. When that request answered
before its mint finished, workerd dropped the mint, and the shared promise
never settled. Every later request in the isolate that found no token
waited on it: in local workerd, the next two requests got no answer in
40 and 10 seconds.

The isolate cache now holds finished values only: a token with its expiry,
and the failure window. A mint in flight stays with the credential of the
request that started it. Reads in one request still share one mint. Two
requests that find no token each mint their own, and a failure that one
request records while the other mints keeps the window to one event.
@harlan-zw
harlan-zw changed the base branch from fix/app-mint-resilience to main October 7, 2026 03:37
Either request can sign its JWT first, so either can mint the first token. The test now checks that each request read with the token its own request minted.
@harlan-zw

Copy link
Copy Markdown
Collaborator Author

Agent note: a check by hand in local workerd (wrangler dev), since CI cannot cover it. A mock GitHub answered each mint after 1.5 s. The same probe Worker ran the code from before this change and after it.

  • Two requests that find no token, 300 ms apart: before, both read with one mint. After, each read with its own. Both builds answered 200 to both.
  • Request A answers after 400 ms while its mint is in flight. Request B arrives during that mint. Before: workerd cancelled A's mint, B got no answer in 40 s, and a later request C got no answer in 10 s. After: B minted its own token and answered in 1.5 s, and C read the cached token in 28 ms.
  • A client that disconnects did not end its request in local workerd, so that case passed in both builds.

@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 7, 2026
@harlan-github-agent harlan-github-agent Bot added 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 7, 2026
@harlan-zw
harlan-zw merged commit a498953 into main Oct 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant