Skip to content

fix(ci): publish without attestations from the called workflow - #80

Merged
nadeem4 merged 1 commit into
mainfrom
fix/pypi-publish-attestations
Sep 7, 2026
Merged

fix(ci): publish without attestations from the called workflow#80
nadeem4 merged 1 commit into
mainfrom
fix/pypi-publish-attestations

Conversation

@nadeem4

@nadeem4 nadeem4 commented Sep 7, 2026

Copy link
Copy Markdown
Owner

v0.1.1 was tagged and a GitHub Release was created, but nothing reached PyPI
(all three projects still show only 0.1.0) and no image reached GHCR.

What failed

Run 34077955471,
publish / pypi (nl2sql-adapter-sdk, ...):

400 Invalid attestations supplied during upload: Could not verify the uploaded
artifact using the included attestation: Verification failed: Certificate's
Build Config URI (<...value=b'...https://github.com/nadeem4/nl2sql/.github/workflows/release_please.yml@refs/heads/main'>)
does not match expected Trusted Publisher (publish_pypi.yaml @ nadeem4/nl2sql)

The matrix's default fail-fast then cancelled the nl2sql-api and
nl2sql-engine legs, and ghcr and docs were skipped. Docs still deployed —
that is the separate Publish Docs workflow.

Why: auth and attestation disagree about which workflow "this" is

release_please.yml reaches publish_pypi.yaml through workflow_call, because
GitHub will not start a workflow from a tag pushed with the default
GITHUB_TOKEN. In that arrangement the two halves of trusted publishing resolve
to different workflows:

Identity used Value here
OIDC auth the token's job_workflow_ref claim — the called workflow publish_pypi.yaml — the registered publisher, so auth succeeds
PEP 740 attestation the Sigstore certificate's Build Config URI — the entry-point workflow release_please.ymlnot a registered publisher

The action itself printed both claims in the same run, before the upload:

##[warning]Potential workflow misconfiguration:
The claims in this token suggest that the calling workflow is a reusable workflow.
In particular, this action was initiated by:
    nadeem4/nl2sql/.github/workflows/publish_pypi.yaml@refs/heads/main
Whereas its parent workflow is:
    nadeem4/nl2sql/.github/workflows/release_please.yml@refs/heads/main
Reusable workflows are **not currently supported** by PyPI's Trusted Publishing
functionality, and are subject to breakage.

Auth was never the problem: the request reached upload.pypi.org/legacy/ and was
rejected with a 400 about the attestation, not a 403 about the credential —
and PyPI's own message names publish_pypi.yaml as the publisher it
authenticated as. The upload signed and logged both artifacts to Rekor
successfully; only PyPI's verification of them failed.

The fix

  1. attestations: false on the pypa/gh-action-pypi-publish step. The
    input defaults to 'true' in action.yml,
    which is why nothing had to opt in to break. The step carries a comment
    explaining the asymmetry so nobody re-enables it.
  2. fail-fast: false on the pypi matrix, so one package's failure can no
    longer cancel the other two and take ghcr and docs with it.

Trade-off

Published artifacts lose PEP 740 provenance. That is a real, if modest, loss.
Trusted publishing itself is unaffected — the OIDC credential still
authenticates every upload, and no API token or stored secret is introduced.

Alternatives ruled out

  • Re-register the Trusted Publisher as release_please.yml — breaks auth.
    Warehouse matches a GitHub publisher on the job_workflow_ref claim
    (warehouse/oidc/models/github.py: "job_workflow_ref": _check_job_workflow_ref),
    and that claim stays publish_pypi.yaml however the workflow is entered. This
    trades a failing attestation check for a failing credential check.

  • Register both workflows as publishers — does not help. PyPI verifies the
    attestation against the publisher that authenticated the request, not against
    any registered publisher. warehouse/attestations/services.py:

    expected_identity = request.oidc_publisher.attestation_identity
    ...
    attestation_model.verify(expected_identity, distribution)

    A second registration is simply never consulted for this comparison.

  • Make publish_pypi.yaml the entry point via a PAT-pushed tag — would keep
    attestations, but reintroduces exactly the stored credential that trusted
    publishing exists to remove. Not worth PEP 740 provenance.

Upstream tracking issue for official reusable-workflow support:
pypa/gh-action-pypi-publish#166.

Also in this PR

  • docs/observability/error-handling.md — line 5 still carried the
    pre-fix(pipeline): keep the error code when a routing error reaches the runtime #78 claim that run_with_graph() folds PipelineExecutionError into
    state as an UNKNOWN_ERROR, contradicting line 43 in the same file. Corrected
    to the shipped behaviour in
    packages/nl2sql/src/nl2sql/pipeline/runtime.py (return {"errors": [e.error]}).
    docs/architecture/failure_recovery.md and docs/architecture/pipeline.md
    were checked and are already correct.
  • docs/development/releasing.md — documents recovering a release that
    tagged but never uploaded (dispatch the existing tag; workflow_dispatch with
    a required tag input is present in the workflow's on: block), and records
    why attestations are off.

Verification

  • All four workflow files parse; publish_pypi.yaml still exposes push,
    workflow_call and workflow_dispatch, and fail-fast: false /
    attestations: false are set where intended.
  • Unit suite twice: 266 passed, 1 skipped, 47 deselected.
  • Key-free integration suite twice: 28 passed, 286 deselected.

The publish path itself is not verifiable from CI. No workflow run
exercises a PyPI upload, so the real test is a workflow_dispatch of
publish_pypi.yaml after this merges — a deliberate, irreversible, public
action left to the maintainer:

$ gh workflow run publish_pypi.yaml -f tag=v0.1.1

@nadeem4
nadeem4 merged commit 2bcdbe8 into main Sep 7, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/pypi-publish-attestations branch September 7, 2026 03:33
@github-actions github-actions Bot mentioned this pull request Sep 7, 2026
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