Skip to content

ci(preview): publish Python previews to TestPyPI with trusted publishing - #3147

Merged
jpr5 merged 1 commit into
mainfrom
ci/testpypi-trusted-publishing
Oct 22, 2026
Merged

jpr5 merged 1 commit into
mainfrom
ci/testpypi-trusted-publishing

Conversation

@jpr5

@jpr5 jpr5 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

What

Switch the TestPyPI preview publish (.github/workflows/publish-python-preview.yml) from the long-lived TEST_PYPI_API_TOKEN secret to trusted publishing (GitHub OIDC). This is the follow-up that #3146 left out of scope.

  • The Publish all packages to TestPyPI step no longer sets UV_PUBLISH_TOKEN.
  • It now runs uv publish --trusted-publishing always. The --publish-url https://test.pypi.org/legacy/ and --check-url https://test.pypi.org/simple/ stay the same. With always, a missing or mismatched publisher fails the step with TestPyPI's error. The existing Post failure comment step then reports it on the PR.
  • The publish job gets id-token: write. environment: pypi and the file name do not change. The trusted publishers are bound to both, and a comment now says so.
  • The TEST_PYPI_API_TOKEN secret and the TestPyPI token are not deleted. See the rollout order below.

build-python-preview.yml builds only six packages, and all six already exist on TestPyPI. So the concern in #3146 about packages with no TestPyPI project yet does not apply to this workflow today. If a package is added to the preview build later, it needs a trusted publisher (or a pending publisher) on TestPyPI first.

Trusted publishers on test.pypi.org (already added)

These were added as the TestPyPI owner CopilotKit and checked by reloading each project's Publishing page. None of the projects had a publisher before, so there were no stale ones.

TestPyPI project Owner Repository Workflow Environment
ag-ui-protocol ag-ui-protocol ag-ui publish-python-preview.yml pypi
ag-ui-langgraph ag-ui-protocol ag-ui publish-python-preview.yml pypi
ag-ui-crewai ag-ui-protocol ag-ui publish-python-preview.yml pypi
ag-ui-agent-spec ag-ui-protocol ag-ui publish-python-preview.yml pypi
ag_ui_adk ag-ui-protocol ag-ui publish-python-preview.yml pypi
ag_ui_strands ag-ui-protocol ag-ui publish-python-preview.yml pypi

RED: before this change

A probe ran the Publish all packages to TestPyPI step exactly as the YAML has it. It used uv 0.12.1 (the version the workflow pins), real TestPyPI, a fake token, a dummy ag-ui-tp-probe-do-not-publish dist, and a simulated GitHub Actions OIDC environment. A local fake ACTIONS_ID_TOKEN_REQUEST_URL endpoint recorded requests.

## job permissions: {"actions" => "read", "pull-requests" => "write"}
## job environment: "pypi"
## step env keys: UV_PUBLISH_TOKEN
   uv publish \
     --publish-url https://test.pypi.org/legacy/ \
     --check-url https://test.pypi.org/simple/ \
## uv on PATH: uv 0.12.1
Publishing 2 files to https://test.pypi.org/legacy/
Uploading ag_ui_tp_probe_do_not_publish-0.0.0-py2.py3-none-any.whl (1.1KiB)
error: Failed to publish `dist-preview/ag_ui_tp_probe_do_not_publish-0.0.0-py2.py3-none-any.whl` to
       https://test.pypi.org/legacy/
  Caused by: Server returned status code 403 Forbidden. Server says: 403 Invalid or non-existent authentication information.
## step exit status: 2 (failure)
## GitHub OIDC token requests seen by local endpoint: 0

The token path uploads with the token and never asks GitHub for an OIDC token.

GREEN: after this change

Same probe, same step, same dist:

## job permissions: {"actions" => "read", "pull-requests" => "write", "id-token" => "write"}
## job environment: "pypi"
## step env keys: (none)
   uv publish \
     --trusted-publishing always \
     --publish-url https://test.pypi.org/legacy/ \
     --check-url https://test.pypi.org/simple/ \
## uv on PATH: uv 0.12.1
Publishing 2 files to https://test.pypi.org/legacy/
error: Failed to obtain token for trusted publishing
  Caused by: Server returned error code 422 Unprocessable Entity, and the OIDC has an unexpected format.
    Response: {"errors":[{"code":"invalid-payload","description":"malformed JWT"}],"message":"Token request failed"}
## step exit status: 2 (failure)
## GitHub OIDC token requests seen by local endpoint: 1
   GET /oidc-token?api-version=2.0&audience=testpypi HTTP/1.1  (Authorization: Bearer fake-runner-request-token)

uv read the audience (testpypi) from real test.pypi.org, asked the GitHub OIDC endpoint for an id-token, and sent it to real test.pypi.org to mint an upload token. TestPyPI rejected the fake JWT, so no upload was attempted. https://test.pypi.org/pypi/ag-ui-tp-probe-do-not-publish/json returns 404 after both runs, so nothing was published.

Other checks:

  • actionlint 1.7.12 passed on every workflow that lint-release-workflows.yml checks (exit 0).
  • zizmor 1.26.1 with .github/zizmor.yml reports no findings on this file. On main it reports one: info[use-trusted-publishing]: prefer trusted publishing for authentication. This change clears it.
  • scripts/release/verify-python-toolchain-pins.sh passed.

What this does not prove: a real exchange with a GitHub-issued token. The first preview run after merge proves that.

Rollout order

  1. Add the trusted publishers on TestPyPI. Done (table above).
  2. Merge this PR.
  3. The next preview run proves it. The first Publish Python Preview to TestPyPI run after merge must show uv publish succeeding with no UV_PUBLISH_TOKEN in the step env, and the install-instructions comment on the PR.
  4. Then delete the TEST_PYPI_API_TOKEN repo secret and revoke the test package creation API token on test.pypi.org (scope: all projects, owner CopilotKit).

Drop the long-lived TEST_PYPI_API_TOKEN from the preview publish step and
run uv publish with --trusted-publishing always against the same TestPyPI
upload and check URLs. The publish job gains id-token: write; it keeps
environment: pypi and this file name, which the trusted publisher on each
of the six TestPyPI projects is bound to.
@jpr5
jpr5 requested a review from a team as a code owner October 22, 2026 04:07
@pkg-pr-new

pkg-pr-new Bot commented Oct 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@3147

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@3147

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@3147

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@3147

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@3147

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@3147

@ag-ui/adk-js

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk-js@3147

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@3147

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@3147

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@3147

@ag-ui/antigravity

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/antigravity@3147

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@3147

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@3147

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@3147

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@3147

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@3147

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@3147

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@3147

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@3147

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@3147

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@3147

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@3147

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@3147

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@3147

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@3147

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@3147

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@3147

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@3147

commit: 7920535

@jpr5
jpr5 merged commit 76a7e1a into main Oct 22, 2026
21 checks passed
@jpr5
jpr5 deleted the ci/testpypi-trusted-publishing branch October 22, 2026 04:12
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