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
- Check whether the snapshot is already an ancestor before cherry-picking: compare with the table's current snapshot history and skip if present.
- Make the operation idempotent in your workflow code: catch CherrypickAncestorCommitException and treat it as success.
- Cherry-pick onto a different branch whose current snapshot is not already the picked snapshot.
- 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
- Skip cherry-pick when the snapshot ID already appears in the target branch's snapshot history.
- Make retry/idempotency wrappers treat CherrypickAncestorCommitException as success.
- Only cherry-pick snapshots whose parent lineage diverges from the target branch's current ancestor chain.
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
- Deleted manifest %s could not be found in the latest snapsho
- Cannot cherrypick snapshot %s: already picked to create ance
- Found conflicting files that can contain records matching pa
- Invalid distribution mode: %s
- this.getClass().getName() + " doesn't implement removedDelet
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/f21ffae19f2c0459.
Report an issue: GitHub.