Skip to content

fix(sign): fetch existing Rekor entries by default - #1709

Open
lodekeeper wants to merge 1 commit into
sigstore:mainfrom
lodekeeper:fix/rekor-fetch-on-conflict-default
Open

fix(sign): fetch existing Rekor entries by default#1709
lodekeeper wants to merge 1 commit into
sigstore:mainfrom
lodekeeper:fix/rekor-fetch-on-conflict-default

Conversation

@lodekeeper

Copy link
Copy Markdown

Summary

  • default TLogClient to fetching existing Rekor entries on 409 conflicts
  • keep explicit fetchOnConflict: false behavior unchanged
  • add coverage for the default 409 recovery path

Context

This fixes a retry-after-success failure mode seen by npm provenance publishers. If Rekor commits a create-entry request but the client retries after a timeout or transient retryable failure, the retry receives 409 an equivalent entry already exists. The client already has recovery logic for that condition, but the default was fetchOnConflict: false, so high-level callers such as npm provenance publishing treated the benign duplicate as a fatal TLOG_CREATE_ENTRY_ERROR.

Related downstream reports:

Verification

  • npm run build
  • npm test --workspace @sigstore/sign -- witness/tlog/client.test.ts
  • npm run lint:check -- --quiet

Generated with AI assistance.

@lodekeeper
lodekeeper requested a review from a team as a code owner July 28, 2026 15:10
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 58ad28d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sigstore/sign Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@matthewkeil matthewkeil left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!! Thanks @lodekeeper

Signed-off-by: lodekeeper <lodekeeper@users.noreply.github.com>
@lodekeeper
lodekeeper force-pushed the fix/rekor-fetch-on-conflict-default branch from 7f557a6 to 58ad28d Compare July 28, 2026 17:09
@lodekeeper

Copy link
Copy Markdown
Author

Fixed the DCO-only blocker by amending the commit with the required Signed-off-by trailer and force-pushing with lease. New head: 58ad28df103558af1cbfb06952c90431d539318c; DCO is green now.

@matthewkeil

Copy link
Copy Markdown

@bdehamer curious what your thoughts are on this one. There were a few issues across a bunch of repos but wondering if there was a reason this defaulted to false originally.

@lodekeeper

Copy link
Copy Markdown
Author

Some author context on the motivating bug (deferring to @bdehamer on the reason for the original false default):

We hit this as retry-after-success 409 TLOG_CREATE_ENTRY_ERROR in npm provenance publishing. If a run uploads the Rekor entry and the job then fails and retries past that point, the retry re-signs identical content → Rekor 409s the duplicate, and with fetchOnConflict: false that surfaces as a hard error instead of resolving to the existing entry. For a content-deterministic build the retry then stays wedged until the source changes (a plain re-run can't recover). It broke ChainSafe/lodestar's nightly publish-dev provenance job — consistent with the "few issues across a bunch of repos" you mentioned. Defaulting to true makes the duplicate-upload path idempotent.

wemeetagain pushed a commit to ChainSafe/lodestar that referenced this pull request Aug 5, 2026
## Current recommendation

As of 2026-07-28 19:2x UTC, the latest unstable `publish.yml` runs have
green npm-publish jobs again. I do **not** recommend merging this local
`pnpm patch` while 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 `Publish` workflow intermittently failed during dev publishes with:

```text
lerna-lite ERR! TLOG_CREATE_ENTRY_ERROR error creating tlog entry - (409) an equivalent entry already exists in the transparency log with UUID ...
```

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 through `lerna-lite -> libnpmpublish
-> sigstore`). The observed failure matches a **retry-after-success**
path:

1. Rekor creates the entry server-side.
2. The client times out or retries after a transient failure.
3. The retry submits the same equivalent entry.
4. Rekor correctly returns `409 an equivalent entry already exists`.
5. `@sigstore/sign` treats that 409 as fatal because the default Rekor
witness config has `fetchOnConflict: false`.

`@sigstore/sign` already 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 sigstore` to flip the Rekor witness default
used by the Lodestar publish stack:

```diff
   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:

- Issue: sigstore/sigstore-js#1708
- PR: sigstore/sigstore-js#1709

Similar downstream reports:

- SocialGouv/code-du-travail-numerique#7419
- apify/apify-shared-js#649

## Alternatives considered

- **Bump `lerna-lite`/`sigstore`** -- no effect yet; `fetchOnConflict:
false` is still the default in currently consumed versions.
- **Disable provenance on dev** -- drops supply-chain provenance;
rejected.
- **Re-run the job** -- can work for dev publishes when a later commit
changes the tarball/version, but is not a robust stable/RC release
strategy if the same package/version is wedged after Rekor already
recorded the entry.
- **Increase timeout** -- may reduce timeout-triggered retries, but does
not make the Rekor create-entry operation idempotent across
5xx/429/retry-after-success cases.

## Notes

- First `patchedDependencies` entry in the repo.
- Treat this as a hold/emergency patch unless the failure recurs or a
release publish is blocked before upstream is available.

🤖 Generated with AI assistance

---------

Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Matthew Keil <github@mail.matthewkeil.com>
Co-authored-by: matthewkeil <me@matthewkeil.com>
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.

Recover Rekor duplicate-entry conflicts by default

2 participants