Skip to content

fix(ci): refresh pub.dev OIDC token per package publish - #24

Merged
Arenukvern merged 3 commits into
mainfrom
cursor/pub-oidc-per-package-refresh-b4f2
Nov 10, 2026
Merged

Arenukvern merged 3 commits into
mainfrom
cursor/pub-oidc-per-package-refresh-b4f2

Conversation

@Arenukvern

@Arenukvern Arenukvern commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

The pub_publish job uploads ~12 train packages in one job. dart-lang/setup-dart mints pub.dev OIDC once at job start; after ~9 minutes of propagation waits the JWT expires (Invalid JWT token: invalid timestamps on the last package in the v7.0.2 run).

This PR:

  • Mints a fresh OIDC token from ACTIONS_ID_TOKEN_REQUEST_URL with audience=https://pub.dev immediately before each dart pub publish (after any inter-package propagation wait for the previous package).
  • Configures credentials via dart pub token add https://pub.dev --env-var PUB_TOKEN.
  • Masks the token with ::add-mask:: when GITHUB_ACTIONS=true.
  • Fails clearly when --publish runs without OIDC env (no change to --dry-run).
  • Does not set step-level env for ACTIONS_ID_TOKEN_* (those expressions are empty in the workflow env context and would overwrite runner-injected values).
  • Retries the post-publish check_package_consumer.sh --hosted step with exponential backoff (15s start, 60s cap, 600s total) for pub.dev indexing lag.
  • Adds tool/contracts/test_publish_packages_oidc.sh (wired into check_contracts.sh).
  • Updates test/release_train_test.dart fixtures (fake OIDC env + curl/dart) so publish integration tests match the guard.

No version/manifest changes; no publishing.

Files changed

  • tool/release/publish_packages.sh — per-package OIDC refresh + masking
  • tool/ci/check_package_consumer.sh — hosted-mode retry loop
  • .github/workflows/pub_publish.yml — hosted consumer timeout only on verify step
  • tool/contracts/test_publish_packages_oidc.sh — OIDC/publish wiring contracts
  • tool/contracts/check_contracts.sh — runs OIDC contract test
  • test/release_train_test.dart — OIDC-aware publish train fixture + test

Verification

bash -n

bash -n tool/release/publish_packages.sh tool/ci/check_package_consumer.sh tool/contracts/test_publish_packages_oidc.sh
# OK

OIDC contract test

$ bash tool/contracts/test_publish_packages_oidc.sh
test_publish_packages_oidc: publish OIDC contracts ok

--publish without OIDC (expect exit 1)

$ bash tool/release/publish_packages.sh --publish
check_version: all release touchpoints match VERSION (0.9.1)
publish_packages: --publish requires GitHub Actions OIDC env
publish_packages: set ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN (job needs id-token: write)
exit=1

dart pub get --no-example + dry-run

$ bash tool/release/publish_packages.sh --dry-run
# 12 packages (exit 0)

Contract gates

$ bash tool/contracts/check_contracts.sh
check-contracts: all gates passed

actionlint

$ actionlint .github/workflows/pub_publish.yml
# exit 0

Dart tests

$ dart test test/release_train_test.dart
# 9 passed

$ dart test   # root suite
# +710 ~9: All tests passed!

Type of change

  • Bug fix (fix:)

Checklist

  • Conventional commit title
  • No package version / manifest edits
  • check_contracts.sh run locally (includes OIDC contract)
  • release_train_test.dart + root dart test green
Open in Web Open in Cursor 

cursoragent and others added 2 commits November 10, 2026 02:24
Mint a fresh GitHub Actions ID token immediately before each train
package upload so long inter-package waits cannot expire the JWT.
Add bounded retry with exponential backoff to the hosted consumer
gate for pub.dev indexing lag.

Co-authored-by: Anton Malofeev <anton@xsoulspace.dev>
Stop overriding runner-injected ACTIONS_ID_TOKEN_* in pub_publish.yml;
mask minted tokens in GITHUB_ACTIONS; add test_publish_packages_oidc.sh.

Co-authored-by: Anton Malofeev <anton@xsoulspace.dev>

@Arenukvern Arenukvern left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Flutter Mcp Toolkit steward: approve at ddbf2569. I'm posting this as a comment because GitHub treats the shared account as the author. It's the same fix as mcp_flutter #162, and the ordering is right.

  • refresh_pub_oidc_token runs inside the loop, after the skip-if-published check and right before dart pub publish --force. Because wait_for_hosted_version ends the previous iteration, every package gets a token that is only a few seconds old, however long the train runs.
  • local response token is declared before the assignments, so a curl --fail or a parse failure still exits under set -e instead of being swallowed by local.
  • The token is masked before it's exported, and dart pub token add gets it through --env-var, so it never appears on a command line. python3 is on ubuntu runners, so that's fine.
  • Good catch on ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }}. That would have blanked the real runner values. The contract test guarding it is the right kind of regression net.

One non-blocking follow-up: the hosted-consumer retry wraps pub get, analyze and run together, so a real analyzer or runtime regression keeps retrying for 10 minutes before it goes red. Retrying only while dart pub get fails to resolve, and failing analyze/run on the first error, would keep the indexing-lag cover and give real breakage a fast signal.

The PR currently shows mergeable_state: unstable, so please merge pinned to this SHA only once its checks are green.

Fake curl mints OIDC tokens; fake dart records pub token add before each
publish; add coverage for missing OIDC env on --publish.

Co-authored-by: Anton Malofeev <anton@xsoulspace.dev>

@Arenukvern Arenukvern left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Flutter Mcp Toolkit steward: approve at 02da4e85, as long as CI is green at this SHA. I'm posting this as a comment because GitHub treats the shared account as the author. I compared ddbf2569...02da4e85. It's one commit, and the only file touched is test/release_train_test.dart, so the script and workflow I approved are unchanged.

The fixture fix is the right one. It gives the test fake OIDC env so the guard can stay as it is. The ordering helper also rejects a publish that has no refresh right before it: the line after each oidc| has to be that package's publish --force. Together with the exact dartPackages lists, a skipped package can't sneak in a refresh, and a published package can't miss one.

Two non-blocking hardening ideas for the follow-up:

  • The fake curl answers any call whose arguments contain id-token, so if the script got audience=https://pub.dev or the bearer header wrong, the test would still pass, and only a real publish would catch it. Asserting that the logged OIDC call contains both of those would close that gap.
  • dartPackages now keeps only lines containing pub publish, so an unexpected extra dart call in publish mode would no longer show up. Asserting the full log shape, which is only oidc| and publish --force lines, would put that strictness back.

Right now test, linux-desktop-dev and android-real-smoke are still running, and the other checks are green or skipped.

@Arenukvern
Arenukvern merged commit 6edd33d into main Nov 10, 2026
9 checks passed
@Arenukvern
Arenukvern deleted the cursor/pub-oidc-per-package-refresh-b4f2 branch November 10, 2026 02:49
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.

2 participants