apache/iceberg · error · CherrypickAncestorCommitException

Cannot cherrypick snapshot %s: already an ancestor

Error message

Cannot cherrypick snapshot %s: already an ancestor

What it means

CherryPickOperation.validateNonAncestor rejects cherry-picking a snapshot that is already an ancestor of the table's current state, because re-applying it would be a no-op/conflict. If the target snapshot is the current ancestor chain, CherrypickAncestorCommitException is thrown — either for the snapshot itself or for an ancestor already produced by the same source snapshot. This validation happens during the apply/validate phase of the cherry-pick scan task.

Source

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

    }

    boolean isFastForward = isFastForward(base);
    if (requireFastForward || isFastForward) {
      ValidationException.check(
          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(

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check whether the snapshot is already an ancestor before cherry-picking: compare with the table's current snapshot history and skip if present.
  2. Make the operation idempotent in your workflow code: catch CherrypickAncestorCommitException and treat it as success.
  3. Cherry-pick onto a different branch whose current snapshot is not already the picked snapshot.
  4. Use rollback to the intended ancestor instead of cherry-pick when the goal is restoring existing history.

Example fix

// before
table.manageSnapshots().cherryPick(snapshotId).commit(); // throws if already ancestor

// after
Snapshot picked = table.snapshot(snapshotId);
boolean alreadyAncestor = ((HasSnapshotOperations) table).operations().current()
    .currentSnapshotsAsIds().contains(snapshotId);
if (!alreadyAncestor) {
  table.manageSnapshots().cherryPick(snapshotId).commit();
}
Defensive patterns

Strategy: validation

Validate before calling

TableMetadata meta = ((HasTableOperations) table).operations().current();
boolean isAncestor = meta.currentSnapshotsAsIds().stream()
    .anyMatch(id -> id == snapshotId) // plus check via history/current-ancestor chain
    || org.apache.iceberg.events.Listeners.instance()
        != null; // caller-specific ancestor check via snapshot log
boolean alreadyPicked = meta.snapshots().stream()
    .anyMatch(s -> s.snapshotId() == snapshotId);

Try / catch

try {
  table.manageSnapshots().cherryPick(snapshotId).commit();
} catch (CherrypickAncestorCommitException e) {
  // snapshot is already an ancestor — treat as no-op
  LOG.info("Snapshot {} already in current history; skipping cherry-pick", snapshotId);
}

Prevention

When it happens

Trigger: Calling cherryPick(snapshotId) (RemoveSnapshots/ManageSnapshots cherryPick) with a snapshot ID that is already in the current table's ancestor chain, or a snapshot whose source-snapshot-id already produced an existing ancestor.

Common situations: Re-running the same cherry-pick operation twice; picking a snapshot from the main branch back onto main itself; fast-forward-style workflows where the branch was already rolled back into the current history.

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/f21ffae19f2c0459. Report an issue: GitHub.