A PersistentSnapshotDeletionPolicy re-opened over its save file counts the persisted snapshots, but getSnapshots() is empty and getIndexCommit(gen) returns null until an IndexWriter has opened with it, and no javadoc states this precondition (documentation gap; the behaviour is by design)

Finding LFV-890Area: index / deletion policies (SnapshotDeletionPolicy and PersistentSnapshotDeletionPolicy restart lifecycle, javadoc)Priority: lowImpact: minorNovelty: new

Finding page for LFV-890

AI-assisted finding, confirmed by automated checks, not yet triaged by a human Lucene committer. This site files nothing upstream.

What it shows

You restart an application that uses PersistentSnapshotDeletionPolicy, create the policy again over its save file with OpenMode.APPEND (whose javadoc says this opens the existing snapshots, 'initializing the class with the snapshots information'), and ask it which commits are snapshotted before you open an IndexWriter with it. You expect getSnapshots() and getIndexCommit(gen) to show the persisted snapshot that getSnapshotCount() counts, or the javadoc to say that the policy must first be passed to an IndexWriter. Instead getSnapshotCount() is 1 while getSnapshots() is empty and getIndexCommit(gen) returns null, which its javadoc says means 'not currently snapshotted'; this is by design (the IndexCommit objects come from the writer's onInit call), but no javadoc states the precondition.

The test shows that a PersistentSnapshotDeletionPolicy re-opened over its save file (OpenMode.APPEND) counts the persisted snapshot but getIndexCommit(gen) returns null until an IndexWriter has opened with it, which no javadoc states (TestReopenedPersistentSnapshotGetIndexCommit#testGetIndexCommitBeforeWriterOpens; documentation gap, behaviour by design).

Expected vs actual

Expected the snapshotted IndexCommit (javadoc: null only if "not currently snapshotted"); actual null while getSnapshotCount() is 1, with -ea and -da.

Files

Run it

# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed 5EED0890
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=5EED0890 \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.index.TestReopenedPersistentSnapshotGetIndexCommit

Gradle alternative: copy the test into the matching module's src/test/ tree of the checkout and run ./gradlew -p lucene/<module> test --tests <class> -Ptests.seed=<seed> (add -Ptests.asserts=false for a run without assertions).

Formal models and scripts this finding's part of the hunt used: snapshot-deletion-policies-commit-retention-model-based

Feedback

Open an issue in lucene-ai-bug-hunt. Please do not open issues or pull requests in apache/lucene about this finding unless a Lucene committer agrees it should be filed.

Snapshot of 2026-11-04. AI-assisted; not yet triaged by a human Lucene committer. Feedback: open an issue in lucene-ai-bug-hunt.