chore: handle Rekor 409 conflict during npm provenance publish - #9703
Conversation
npm provenance uploads a signed entry to the Sigstore Rekor transparency log via @sigstore/sign (pulled in by lerna-lite -> libnpmpublish -> sigstore). When Rekor is slow the POST is retried; the retry hits the already-created entry and comes back 409, which the sigstore client turns into a fatal TLOG_CREATE_ENTRY_ERROR. This has broken every dev publish since 2026-07-23 (ChainSafe#9670, ChainSafe#9699, ChainSafe#9606, ChainSafe#9697), each with a different Rekor UUID (retry-after-success, not a version collision). @sigstore/sign already fetches the existing entry on 409, but only when fetchOnConflict is true. The sigstore wrapper hardcodes it to false in initWitnesses() at every reachable version (4.0.0, 4.1.1, main), so a dependency bump does not change the behavior. Patch sigstore to set fetchOnConflict: true so the 409 fetches the valid existing entry and the publish proceeds. Provenance is preserved and the attestation is unchanged. 🤖 Generated with AI assistance Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@lodekeeper please identify the root cause of the failures. Something seems wrong with your analysis and a patch seems like the wrong fix. Why did it just start to fail on the last 4 runs? |
|
(Duplicate reply — apologies for the noise. See the follow-up comment below for the complete root-cause analysis and recommendation.) |
|
@matthewkeil You're right to push back — the patch treats a symptom, not the cause. Here's the root cause. It's not our code. The
Why the last ~4 runs and not before:
So the trigger is external: Rekor write latency crossed the 5s client timeout (or a transient 5xx/429) around 07-23. Sigstore's status page showed no incident, but elevated write latency wouldn't necessarily post there. On the fix: My recommendation: hold this PR and let the next dev publish be the test. If the Rekor latency was transient it self-heals with no patch at all; if it persists, the fix belongs upstream (default |
|
@lodekeeper there seem to be green ci runs on unstable again, please confirm, and reconsider if we need this pr |
|
Confirmed the state — but the "green again" read only holds for the last two runs; the npm-publish job still failed within the last hour with the exact same bug. Run-level status is misleading here because the Docker Hub jobs fail independently of npm publish, so here's the job-level breakdown of the last 5
So the npm-publish job failed at 16:40:26Z — ~20 min before your comment — with the same signature and same Rekor tree UUID ( This is the self-heal behavior, not a resolution: the dev version is SHA-deterministic, so a wedged run only clears when the next commit pushes a new tarball (new tlog leaf). Each publish is still a coin-flip on whether Rekor commits-but-responds-late and trips the retry-after-success 409 — 16:53 and 17:04 just didn't hit the slow-Rekor race; 16:37 did. Recommendation: keep the PR / hold, don't close it.
Happy to file the upstream sigstore/rekor issue in parallel, but I wouldn't drop our defensive patch while the failure is still live on |
|
Upstream tracking/fix is now filed:
The upstream PR changes I also updated this PR body to reference the upstream issue/PR and the similar downstream SocialGouv/code-du-travail-numerique#7419 + apify/apify-shared-js#649 reports. 🤖 Generated with AI assistance |
|
Fresh check after the latest
So my recommendation changes from “keep/hold because the failure is still live” to: do not merge this Lodestar-local patch while the current signal is green; keep the PR only as a ready emergency patch and pointer to the upstream fix. The stable/RC concern is still the reason not to delete the work outright: if a release publish hits retry-after-success before upstream ships and is consumed, re-running the same package/version can keep colliding with the Rekor entry that already exists. But carrying the first local I updated the PR body to reflect this hold/emergency-patch status instead of the older “dev publish is currently failing every run” framing. 🤖 Generated with AI assistance |
libnpmpublish declares sigstore ^4.0.0, so a lockfile refresh could float it to 4.1.x and orphan patches/sigstore@4.0.0.patch (pnpm then fails the install with an unused-patch error). Pin the resolution exactly so the patch and the version travel together. Remove the override alongside the patch once sigstore-js#1709 ships and is consumed downstream.
nflaig
left a comment
There was a problem hiding this comment.
latest ci run on unstable failed due to this again, can we merge this?
|
🎉 This PR is included in v1.46.0 🎉 |
Current recommendation
As of 2026-07-28 19:2x UTC, the latest unstable
publish.ymlruns have green npm-publish jobs again. I do not recommend merging this localpnpm patchwhile the current signal is green.Keep this PR open as a ready emergency downstream patch and as the Lodestar pointer to the upstream sigstore-js fix. Merge it only if the Rekor 409 provenance failure recurs before the upstream fix is released/consumed, or if an RC/stable release is blocked by this exact retry-after-success failure mode.
Close/drop this PR once sigstore-js ships the upstream default change and npm/libnpmpublish/lerna-lite consume a version that enables the Rekor conflict recovery path without a Lodestar-local patch.
Problem
The
Publishworkflow intermittently failed during dev publishes with:The failure was observed across multiple unstable publishes on 2026-07-23 and 2026-07-24. Later unstable publishes are green again, which points to a transient Rekor/signing-path trigger rather than a deterministic Lodestar publishing regression.
Root cause
npm provenance uploads a signed entry to the Sigstore Rekor transparency log via
@sigstore/sign(pulled in throughlerna-lite -> libnpmpublish -> sigstore). The observed failure matches a retry-after-success path:409 an equivalent entry already exists.@sigstore/signtreats that 409 as fatal because the default Rekor witness config hasfetchOnConflict: false.@sigstore/signalready has the needed recovery behavior: on 409 it can fetch the existing entry and continue. The issue is that the high-level default disables it.Emergency downstream patch
This PR uses
pnpm patch sigstoreto flip the Rekor witness default used by the Lodestar publish stack:new RekorWitness({ rekorBaseURL: options.rekorURL, - fetchOnConflict: false, + fetchOnConflict: true, retry: options.retry ?? DEFAULT_RETRY, timeout: options.timeout ?? DEFAULT_TIMEOUT, })On a 409 the client fetches the already-created Rekor entry and the publish proceeds. Provenance is preserved; the client uses the entry Rekor already recorded.
Upstream
Opened upstream sigstore-js tracking and fix:
Similar downstream reports:
Alternatives considered
lerna-lite/sigstore-- no effect yet;fetchOnConflict: falseis still the default in currently consumed versions.Notes
patchedDependenciesentry in the repo.🤖 Generated with AI assistance