fix: re-mint OIDC token in post hook to avoid 401 on long jobs - #6
Merged
Conversation
The post hook was replaying the OIDC JWT minted in `main` via
`core.saveState('token', ...)`. GitHub Actions runtime ID tokens are
short-lived (≲10 min), so any job that ran longer than the JWT's
lifetime hit `HTTP 401` from kobe on `Releasing cluster`, silently
leaving the lease bound until its TTL expired and throttling
concurrent CI.
Persist `audience` in state instead and re-mint a fresh token via
`getOidcToken(audience)` from inside `post.ts`. The
`ACTIONS_ID_TOKEN_REQUEST_URL` / `_TOKEN` env vars used by
`getOidcToken` are job-scoped and remain available in the post step.
If minting fails (e.g. `id-token: write` was dropped), warn and skip
the release rather than throwing — the post hook runs in `always()`
and a throw would mark the cleanup as failed.
Refactor `post()` to a named export with a `require.main === module`
entrypoint guard so it's testable, and add `src/post.test.ts` pinning
the wiring contract (audience round-trip, fresh token passed to
KobeClient, default audience, claim-failed short-circuit, mint-failure
warning).
Repro: Zondax/kunobi-frontend#1606 run 25740506269 — lease acquired
14:22:03, release attempted 14:29:06 (~7 min later), 401.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
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.
Root cause
post.tsreads the OIDC JWT fromcore.getState('token')— the same JWT thatmain.tsminted at lease time viagetOidcToken(audience)and stashed withcore.saveState('token', ...).GitHub Actions runtime ID tokens are short-lived (≲10 min). Jobs that run longer than the JWT's lifetime hit
HTTP 401against kobe's audience-validating endpoint onDELETE /v1/leases/:id. The currentreleaseLeaseonlycore.warnings on non-2xx/404 (deliberate, for post-hook safety), so the symptom is a silent ⚠ in the job log and a lease that stays bound until its TTL expires — throttling concurrent CI on the affected pool.Repro
Zondax/kunobi-frontend#1606run25740506269:~7 min between mint and release — right at the OIDC TTL boundary, which is why it sometimes works and sometimes doesn't.
Fix
main.tsnow savesaudienceto state (not the JWT). The token itself never round-trips through$GITHUB_STATEanymore.post.tsre-mints a fresh OIDC token viagetOidcToken(audience)and uses that for the release call. TheACTIONS_ID_TOKEN_REQUEST_URL/_TOKENenv vars are job-scoped (not step-scoped), so they're still available in the post step.id-token: writewas somehow dropped), warn and skip — never throw out ofalways()cleanup.post()becomes a named export with arequire.main === moduleentrypoint guard so it's unit-testable.Tests
New
src/post.test.tspins the wiring contract (any future regression that re-introduces a cached-token replay will fail the suite):KobeClientkobe-systemwhen state was written by an oldermainmainsaved onesaveState131 tests pass (6 new).
post.tscoverage: 0% → 94%. All gated files still meet their existing floors.Compatibility
The action's public interface is unchanged. The state schema swaps
tokenforaudience; that schema is internal (onlymain.tswrites it and onlypost.tsreads it, in the same job). Existing consumers don't need to change anything in their workflow YAML.Follow-up suggestion (separate PR)
After this lands, consider cutting
v2.1.3and force-updating thev2major tag so every consumer picks it up without bumping theiruses:pin.