{"record":{"id":"8e3d299ea4e6329e","repo":"apache/iceberg","slug":"cannot-cherrypick-snapshot-s-already-picked-to-c","errorCode":null,"errorMessage":"Cannot cherrypick snapshot %s: already picked to create ancestor %s","messagePattern":"Cannot cherrypick snapshot (.+?): already picked to create ancestor (.+?)","errorType":"validation","errorClass":"CherrypickAncestorCommitException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/CherryPickOperation.java","lineNumber":214,"sourceCode":"          isFastForward,\n          \"Cannot cherry-pick snapshot %s: not append, dynamic overwrite, or fast-forward\",\n          cherrypickSnapshot.snapshotId());\n      return base.snapshot(cherrypickSnapshot.snapshotId());\n    } else {\n      // validate(TableMetadata) is called in apply(TableMetadata) after this apply refreshes the\n      // table state\n      return super.apply();\n    }\n  }\n\n  private static void validateNonAncestor(TableMetadata meta, long snapshotId) {\n    if (isCurrentAncestor(meta, snapshotId)) {\n      throw new CherrypickAncestorCommitException(snapshotId);\n    }\n\n    Long ancestorId = lookupAncestorBySourceSnapshot(meta, snapshotId);\n    if (ancestorId != null) {\n      throw new CherrypickAncestorCommitException(snapshotId, ancestorId);\n    }\n  }\n\n  private static void validateReplacedPartitions(\n      TableMetadata meta, Long parentId, PartitionSet replacedPartitions, FileIO io) {\n    if (replacedPartitions != null && meta.currentSnapshot() != null) {\n      ValidationException.check(\n          parentId == null || isCurrentAncestor(meta, parentId),\n          \"Cannot cherry-pick overwrite, based on non-ancestor of the current state: %s\",\n          parentId);\n      List<Snapshot> snapshots =\n          Lists.newArrayList(\n              SnapshotUtil.ancestorsBetween(\n                  meta.currentSnapshot().snapshotId(), parentId, meta::snapshot));\n      if (!snapshots.isEmpty()) {\n        Iterable<CloseableIterable<DataFile>> addedFileTasks =\n            Iterables.concat(\n                Iterables.transform(","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/CherryPickOperation.java#L196-L232","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Do not cherry-pick the same snapshot twice; check ancestry before calling cherryPick.","Inspect the exception's createAncestorId to find the existing commit and confirm the change is already applied.","If you intentionally want the change again, create a new snapshot (re-write or re-commit) rather than re-picking.","Fast-forward/rollback the target branch instead of cherry-picking if the intent is to reach the source state."],"exampleFix":"// before\ntable.cherryPick(snapshotId); // may already be picked\n// after\nif (!SnapshotUtil.currentAncestorIds(table).contains(snapshotId)) {\n  table.cherryPick(snapshotId);\n}","handlingStrategy":"validation","validationCode":"List<Long> ancestors = SnapshotUtil.currentAncestorIds(table);\nif (ancestors.contains(snapshotId)) {\n  throw new IllegalStateException(\"snapshot already picked: \" + snapshotId);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Track which snapshots have been cherry-picked in your pipeline state.","Make cherry-pick operations idempotent at the application level.","Check for CherrypickAncestorCommitException and treat it as already-applied."],"tags":["cherry-pick","snapshot","ancestry"],"backgroundTag":"invalid-state-transition","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}