{"record":{"id":"a0f8818c3e3141bd","repo":"apache/iceberg","slug":"removing-invalid-ref-snapshot-does-not-exis","errorCode":null,"errorMessage":"Removing invalid ref {}: snapshot {} does not exist","messagePattern":"Removing invalid ref (.+?): snapshot (.+?) does not exist","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/src/main/java/org/apache/iceberg/RemoveSnapshots.java","lineNumber":296,"sourceCode":"  private Map<String, SnapshotRef> computeRetainedRefs(Map<String, SnapshotRef> refs) {\n    Map<String, SnapshotRef> retainedRefs = Maps.newHashMap();\n    for (Map.Entry<String, SnapshotRef> refEntry : refs.entrySet()) {\n      String name = refEntry.getKey();\n      SnapshotRef ref = refEntry.getValue();\n      if (name.equals(SnapshotRef.MAIN_BRANCH)) {\n        retainedRefs.put(name, ref);\n        continue;\n      }\n\n      Snapshot snapshot = base.snapshot(ref.snapshotId());\n      long maxRefAgeMs = ref.maxRefAgeMs() != null ? ref.maxRefAgeMs() : defaultMaxRefAgeMs;\n      if (snapshot != null) {\n        long refAgeMs = now - snapshot.timestampMillis();\n        if (refAgeMs <= maxRefAgeMs) {\n          retainedRefs.put(name, ref);\n        }\n      } else {\n        LOG.warn(\"Removing invalid ref {}: snapshot {} does not exist\", name, ref.snapshotId());\n      }\n    }\n\n    return retainedRefs;\n  }\n\n  private Set<Long> computeAllBranchSnapshotsToRetain(Collection<SnapshotRef> refs) {\n    Set<Long> branchSnapshotsToRetain = Sets.newHashSet();\n    for (SnapshotRef ref : refs) {\n      if (ref.isBranch()) {\n        long expireSnapshotsOlderThan =\n            ref.maxSnapshotAgeMs() != null ? now - ref.maxSnapshotAgeMs() : defaultExpireOlderThan;\n        int minSnapshotsToKeep =\n            ref.minSnapshotsToKeep() != null ? ref.minSnapshotsToKeep() : defaultMinNumSnapshots;\n        branchSnapshotsToRetain.addAll(\n            computeBranchSnapshotsToRetain(\n                ref.snapshotId(), expireSnapshotsOlderThan, minSnapshotsToKeep));\n      }","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/RemoveSnapshots.java#L278-L314","documentation":"During snapshot expiration, RemoveSnapshots.computeRetainedRefs resolves each named ref (branch/tag) to its snapshot. If the ref points to a snapshot ID that no longer exists in metadata, the ref is invalid and is dropped from the retained set with this warning — it will then be removed by the expiration. This normally happens only when metadata was externally modified or corrupted.","triggerScenarios":"Calling expireSnapshots (or removeSnapshots) on a table whose refs reference snapshot IDs missing from snapshot-log — e.g. metadata edited out-of-band, a REST/catalog sync bug, or refs surviving aggressive metadata cleanup.","commonSituations":"Manual JSON metadata editing, third-party tools writing inconsistent metadata, restore/rollback flows interrupted midway, and cross-cluster table copies where only part of the metadata was copied.","solutions":["Verify which refs are invalid: iterate table.refs() and check table.snapshot(ref.snapshotId()) != null; expect the expiration to remove them.","Restore the referenced snapshot by rolling back/re-applying metadata or restoring a metadata backup if the ref must survive.","Rebuild metadata consistency by re-registering the table from a valid metadata file version.","Avoid manual edits to table metadata; use Iceberg APIs (manageSnapshots, rollback) for ref management."],"exampleFix":"// before: relying on a dangling ref\nSnapshot snap = table.snapshot(ref.snapshotId()); // null -> warning, ref dropped\n// after: clean the ref explicitly before expiring\nif (table.snapshot(ref.snapshotId()) == null) {\n  table.manageSnapshots().removeBranch(refName).commit(); // or removeTag\n}","handlingStrategy":"validation","validationCode":"table.refs().forEach((name, ref) -> {\n  if (table.snapshot(ref.snapshotId()) == null) {\n    throw new IllegalStateException(\"Dangling ref \" + name + \" -> \" + ref.snapshotId());\n  }\n});","typeGuard":"boolean refIsValid(Table table, String name) {\n  SnapshotRef ref = table.refs().get(name);\n  return ref != null && table.snapshot(ref.snapshotId()) != null;\n}","tryCatchPattern":"try { table.expireSnapshots().execute(); } catch (IllegalStateException e) { /* repair metadata before retry */ }","preventionTips":["Only manage refs via ManageSnapshots API, never edit metadata JSON","Audit table.refs() vs table.snapshots() before expiration","Avoid interrupted restore/rollback procedures"],"tags":["expiration","refs","metadata","inconsistent-state"],"backgroundTag":"entity-not-found","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}