Skip to content

fix: re-mint OIDC token in post hook to avoid 401 on long jobs - #6

Merged
emmanuelm41 merged 1 commit into
mainfrom
fix/post-fresh-oidc-token
May 12, 2026
Merged

fix: re-mint OIDC token in post hook to avoid 401 on long jobs#6
emmanuelm41 merged 1 commit into
mainfrom
fix/post-fresh-oidc-token

Conversation

@emmanuelm41

Copy link
Copy Markdown
Member

Root cause

post.ts reads the OIDC JWT from core.getState('token') — the same JWT that main.ts minted at lease time via getOidcToken(audience) and stashed with core.saveState('token', ...).

GitHub Actions runtime ID tokens are short-lived (≲10 min). Jobs that run longer than the JWT's lifetime hit HTTP 401 against kobe's audience-validating endpoint on DELETE /v1/leases/:id. The current releaseLease only core.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#1606 run 25740506269:

14:22:03  Lease created: lease-7e7f34a47d05
14:29:06  Releasing cluster (lease: lease-7e7f34a47d05)...
14:29:06  ##[warning]Failed to release lease lease-7e7f34a47d05 (HTTP 401):

~7 min between mint and release — right at the OIDC TTL boundary, which is why it sometimes works and sometimes doesn't.

Fix

  • main.ts now saves audience to state (not the JWT). The token itself never round-trips through $GITHUB_STATE anymore.
  • post.ts re-mints a fresh OIDC token via getOidcToken(audience) and uses that for the release call. The ACTIONS_ID_TOKEN_REQUEST_URL / _TOKEN env vars are job-scoped (not step-scoped), so they're still available in the post step.
  • If mint fails in post (e.g. id-token: write was somehow dropped), warn and skip — never throw out of always() cleanup.
  • post() becomes a named export with a require.main === module entrypoint guard so it's unit-testable.

Tests

New src/post.test.ts pins the wiring contract (any future regression that re-introduces a cached-token replay will fail the suite):

  • mints a fresh OIDC token with the audience saved in state
  • passes the freshly-minted token (not a state-cached one) to KobeClient
  • defaults audience to kobe-system when state was written by an older main
  • uses a custom audience verbatim when main saved one
  • skips release (no throw) when state has no lease — claim failed before saveState
  • warns and skips release when minting a fresh token throws

131 tests pass (6 new). post.ts coverage: 0% → 94%. All gated files still meet their existing floors.

Compatibility

The action's public interface is unchanged. The state schema swaps token for audience; that schema is internal (only main.ts writes it and only post.ts reads 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.3 and force-updating the v2 major tag so every consumer picks it up without bumping their uses: pin.

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.
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.42% 349 / 445
🔵 Statements 78.42% 349 / 445
🔵 Functions 92% 23 / 25
🔵 Branches 88.12% 141 / 160
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/main.ts 0% 0% 0% 0% 1-119
src/post.ts 94.28% 75% 100% 94.28% 48-49
Generated in workflow #6 for commit eec3019 by the Vitest Coverage Report Action

@emmanuelm41
emmanuelm41 merged commit 0ab0b6b into main May 12, 2026
1 check passed
@emmanuelm41
emmanuelm41 deleted the fix/post-fresh-oidc-token branch May 12, 2026 15:16
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