Repository navigation
Description
Self-service
- I'd be willing to implement a fix
Describe the bug
yarn npm publish with provenance (npmPublishProvenance: true) can abort after its attestation has been persisted in Rekor, leaving the package unpublished and an orphan entry in the transparency log:
Some packages failed to publish:
[email protected]
└ YN0001: gM: error creating tlog entry - (409) an equivalent entry already exists
in the transparency log with UUID 108e9186e8c5677a54340083e014f9715fa30a9c88a599e46878bb4c5e68f8f4bdc66337dcd942d7
at wre.createEntry (…/.yarn/releases/yarn-4.18.1.cjs:430:90654)
at async Bre.testify (…/.yarn/releases/yarn-4.18.1.cjs:430:94323)
at async Promise.all (index 0)
at async mX.create (…/.yarn/releases/yarn-4.18.1.cjs:400:20687)
Reconstructed mechanism:
Rekor.createEntryPOSTs to/api/v1/log/entriesthroughfetchWithRetry, which retries 5xx, 408, 429 and network failures.- If Rekor persists the entry but the response is one of those retryable outcomes (or is lost/times out), the retry re-POSTs the identical DSSE entry.
- The second POST is answered with
409 an equivalent entry already exists. TLogClient.createEntrycan recover from exactly this — it catches the conflict and fetches the existing entry — but only whenfetchOnConflictis true, and the witness is built with it hard-coded to false:
new RekorWitness({
rekorBaseURL: t.rekorURL,
entryType: t.legacyCompatibility ? 'intoto' : 'dsse',
fetchOnConflict: !1, // <-- conflict is fatal
retry: t.retry ?? DEFAULT_RETRY,
timeout: t.timeout ?? DEFAULT_TIMEOUT,
})So the conflict surfaces as TLOG_CREATE_ENTRY_ERROR, the publish aborts, and the tarball is never uploaded — even though the attestation it needed is already in the log.
Evidence from the public Rekor API (GET https://rekor.sigstore.dev/api/v1/log/entries/<uuid>):
| attempt | entry UUID | integratedTime | error raised | payloadHash |
|---|---|---|---|---|
| 1 | 108e9186…42d7 |
2026-10-16T16:45:29Z | 16:45:35 | 5ccb61f143f430544777f6df560a456e698517c0a8b4d0ff7e6fbff2dec1f936 |
| 2 | 108e9186…66f7 |
2026-10-16T16:49:53Z | 16:50:00 | 462f820fd1fddb2f5f651c06ebf83b2056cba37492bf40e4b0c8bbd82cef2ace |
- Each error is raised ~6–7 s after the entry it names was created — the shape of a first request that was retried rather than one that simply succeeded.
- The payload hashes differ between the attempts because the statement embeds
runDetails.metadata.invocationId(…/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}) andresolvedDependencies[].digest.gitCommit. Attempt 2 therefore cannot have collided with attempt 1's entry — it collided with its own, i.e. the duplicate is generated inside a single publish, not by a stale log entry. - The version never appears in the registry while its entry exists.
Expected instead, any of:
- treat a conflict as success when the entry is already logged (that is all
attest()needs); - or honour
fetchOnConflict/ default it to true for the publish path — the bundled sigstore client already implements the recovery; - or at minimum, never abort the registry upload when the log entry was written by this very run.
To reproduce
A registry publish can't be reduced to a package.json, so the reproduction is the two public runs plus the Rekor entries above. The essential setup:
- GitHub Actions release workflow with
id-token: write(here npm Trusted Publishing),.yarnrc.ymlwithnpmPublishProvenance: true. - Publish a public workspace whose version is not yet in the registry, one
yarn npm publishper package (here driven bychangeset publish). - Watch for
YN0001: … error creating tlog entry - (409) an equivalent entry already exists.
It is a race, so it is intermittent: the same repository published [email protected] and [email protected] with provenance successfully on 2026-09-24 (yarn 4.17.1), then failed twice in a row ~6 minutes apart on 2026-10-16. Presumably the tlog POST has to be retried for the bug to fire.
Failing runs:
- https://github.com/un-ts/prettier/actions/runs/38934371950 — job
117086020953(attempt 1), job117087882350(attempt 2)
Environment
GitHub Actions: ubuntu-latest (GitHub-hosted), 2026-10-16
Node: 24 (lts/*)
Yarn: 4.18.1 — /home/runner/work/prettier/prettier/.yarn/releases/yarn-4.18.1.cjs
(.yarnrc.yml: nodeLinker: node-modules, npmPublishProvenance: true)
Publish driver: changeset publish (@changesets/[email protected]) -> yarn npm publish
Registry auth: npm Trusted Publishing (OIDC); Fulcio/OIDC signing works, the
failure is at the Rekor write
Additional context
- This is a race, not a deterministic failure, and I do not think it is caused by the yarn 4.17.1 → 4.18.1 upgrade: the publish-side code (
Rekor.createEntry,fetchWithRetry,RekorWitnessconstruction, theTLogClientconflict branch) reads the same across those bundles. What did change is the bundled sigstore client — 4.18.x also carries aTLogV2Clientthat 4.17.1 does not — so I can't rule out a changed retry/timeout policy. The symptom needs a retried POST either way. - The last successful provenance publish in that repository was on 2026-09-24 with yarn 4.17.1; the next release with something to publish is the one that failed, so there is no counter-example in between.
- Our workaround, not yet applied: publish the blocked version once with
npmPublishProvenance: false, then re-enable provenance. - Other ecosystems see the same class of failure with npm's CLI and treat a Rekor 409 as already-published in CI (chore: handle Rekor 409 conflict during npm provenance publish ChainSafe/lodestar#9703, vectorize-io/hindsight
ec384e9, Release 3.1.50 partially published WordPress/wordpress-playground#4269).
Happy to supply the full job logs, the raw Rekor entry bodies, or to test a patch if you want one.
Activity
JounQin commentedon Oct 17, 2026
Update: the same release went through on a later attempt, with nothing changed.
The run from the report was re-run; on attempt 4 (2026-10-17, job started
12:01:38Z, publish step12:01:58Z → 12:02:08Z, same commit, same tarball,same Yarn 4.18.1) the publish completed and the package is now live:
[email protected]is on npm with an attestation —https://registry.npmjs.org/-/npm/v1/attestations/[email protected] —
predicateType: https://slsa.dev/provenance/v1, published2026-10-17T12:02:40Z108e9186e8c5677a00520fd08164a7c68151f49565a7266ebb5e646d6898f787f8b852d38720d493,kind: dsse, integrated2026-10-17T12:02:43Z,payloadHash
6fa7e8a87bc66b0f9344399b20ce8b8e1f988bb955cad91b793c28d7f163046a409was reported on that attempt, and the step took 10 s — i.e. it neverhit the retry path.
That is consistent with the mechanism described above: the failure needs the
tlog POST to be retried. When the first POST is observed as successful, the same
publish writes exactly one entry and finishes normally. Same repository, same
version, same Yarn version — the only difference between the failing attempts
and this one is timing.
Two notes that survive this:
their error messages), so a retry-only diagnosis is still worth handling in
code: a conflict on a retried POST should not abort a publish whose
attestation is already logged, and should not leave the log with entries for
versions that were never published.
unlucky"; treating the conflict as success (or honouring
fetchOnConflict)would turn the current worst case into a normal publish.