apache/iceberg · error · CherrypickAncestorCommitException

Cannot cherrypick snapshot %s: already picked to create ance

Error message

Cannot cherrypick snapshot %s: already picked to create ancestor %s

What it means

Cherry-picking a snapshot requires that the snapshot was not already cherry-picked into the current table's history. CherryPickOperation.validate() checks whether the snapshot is a current ancestor, or whether a previously cherry-picked copy of it already exists as an ancestor (via the source-snapshot-id metadata). Throwing prevents duplicating changes already in the ancestor lineage.

Source

Thrown at core/src/main/java/org/apache/iceberg/CherryPickOperation.java:214

          isFastForward,
          "Cannot cherry-pick snapshot %s: not append, dynamic overwrite, or fast-forward",
          cherrypickSnapshot.snapshotId());
      return base.snapshot(cherrypickSnapshot.snapshotId());
    } else {
      // validate(TableMetadata) is called in apply(TableMetadata) after this apply refreshes the
      // table state
      return super.apply();
    }
  }

  private static void validateNonAncestor(TableMetadata meta, long snapshotId) {
    if (isCurrentAncestor(meta, snapshotId)) {
      throw new CherrypickAncestorCommitException(snapshotId);
    }

    Long ancestorId = lookupAncestorBySourceSnapshot(meta, snapshotId);
    if (ancestorId != null) {
      throw new CherrypickAncestorCommitException(snapshotId, ancestorId);
    }
  }

  private static void validateReplacedPartitions(
      TableMetadata meta, Long parentId, PartitionSet replacedPartitions, FileIO io) {
    if (replacedPartitions != null && meta.currentSnapshot() != null) {
      ValidationException.check(
          parentId == null || isCurrentAncestor(meta, parentId),
          "Cannot cherry-pick overwrite, based on non-ancestor of the current state: %s",
          parentId);
      List<Snapshot> snapshots =
          Lists.newArrayList(
              SnapshotUtil.ancestorsBetween(
                  meta.currentSnapshot().snapshotId(), parentId, meta::snapshot));
      if (!snapshots.isEmpty()) {
        Iterable<CloseableIterable<DataFile>> addedFileTasks =
            Iterables.concat(
                Iterables.transform(

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Do not cherry-pick the same snapshot twice; check ancestry before calling cherryPick.
  2. Inspect the exception's createAncestorId to find the existing commit and confirm the change is already applied.
  3. If you intentionally want the change again, create a new snapshot (re-write or re-commit) rather than re-picking.
  4. Fast-forward/rollback the target branch instead of cherry-picking if the intent is to reach the source state.

Example fix

// before
table.cherryPick(snapshotId); // may already be picked
// after
if (!SnapshotUtil.currentAncestorIds(table).contains(snapshotId)) {
  table.cherryPick(snapshotId);
}
Defensive patterns

Strategy: validation

Validate before calling

List<Long> ancestors = SnapshotUtil.currentAncestorIds(table);
if (ancestors.contains(snapshotId)) {
  throw new IllegalStateException("snapshot already picked: " + snapshotId);
}

Prevention

When it happens

Trigger: Calling table.cherryPick(snapshotId) where snapshotId is already an ancestor of the current snapshot, or where an earlier cherry-pick of the same source snapshot produced an ancestor commit.

Common situations: Re-running a cherry-pick operation after a prior successful run; scripted retry logic that re-invokes cherryPick on the same snapshot id; applying a branch snapshot that was already picked into main.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/8e3d299ea4e6329e. Report an issue: GitHub.