All findings · Lucene finding · LFV-1056 · hnsw-builder-beamwidth1-zero-capacity-queue

With beamWidth = 1 no HNSW graph can be built: the builder sizes a queue beamWidth / 2 = 0, and a merge closes the IndexWriter

The HNSW vector formats accept beamWidth = 1, but since 10.2.0 the graph builder also creates a queue of beamWidth / 2 slots, which is 0, and the queue rejects size 0. So no HNSW graph can be built, and the first merge that needs one closes the IndexWriter.

Summary

Priority
Low, rank 686BeamWidth = 1 is accepted by the core HNSW formats but makes every graph build throw, so a merge closes the IndexWriter (released since 10.2.0), the same accepted-setting crash class as LFV-589.
Impact
crash
Since
10.2.0 (#14331, merged 2025-03-20); 10.1.0 and earlier workintroduced 2025-03-20 · about 1 year
Affected
every release from 10.2.0 to 10.6.0, and main
Status
documented
Found by
code reading of the HNSW graph builder while modelling its parameter domain, found twice independently; confirmed by executed tests

AI-assisted page: this finding was first signalled by code reading or testing and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

beamWidth (also called efConstruction) is how many candidates the HNSW graph builder keeps while it links a new vector into the graph. Since 10.2.0 the builder also keeps an extra queue of half that size for merges, and half of 1 is 0 in integer arithmetic.

Background: how Lucene builds HNSW graphs

  1. Each segment's kNN vectors get an HNSW graph. To insert a vector, the builder searches the graph and keeps the best beamWidth candidates in a priority queue; the default beamWidth is 100, maxConn (M) is 16.
  2. Small segments skip the graph: with the default threshold a segment gets one only from 648 vectors. A merge then builds the graph of the merged segment.
  3. Since 10.2.0, merges that combine existing graphs search level 0 from known neighbours with a smaller queue of min(beamWidth / 2, M * 3) slots. Every builder allocates it up front, whether or not it is used.
Step-by-step diagram of the minimal repro on Lucene main 645d4fe14a20. 1: Lucene99HnswVectorsFormat(16, 1), two flushed segments of 500 vectors (below the 648-vector graph threshold), forceMerge(1). 2: the HnswGraphBuilder constructor allocates three queues; the third is sized Math.min(beamWidth / 2, M * 3) = min(1 / 2, 48) = 0, and TernaryLongHeap throws IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0. 3: on a merge IndexWriter.merge calls tragicEvent and the writer closes; on the flush path the 648th vector document and the next one are rejected (later ones by code reading) while the writer stays open. 4: 10.1.0 passes, 10.2.0 to 10.6.0 and main fail; the fix Math.max(1, ...) passes.
Four steps: you set beamWidth = 1 and merge two small segments; inside, the builder sizes one queue beamWidth / 2, which is 0; every graph build fails (a merge closes the writer; while a segment fills, addDocument rejects the vector documents that need a graph); broken since 10.2.0, with a one-line fix. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. You configure a vector field with Lucene99HnswVectorsFormat(16, 1): the default 16 connections and a beam width of 1, which the format accepts (any value from 1 to 3200). You index 1,000 2-d vectors, flush after 500, and call forceMerge(1).
  2. You expect one merged segment with an HNSW graph, as beamWidth = 2 gives, and as beamWidth = 1 gave up to 10.1.0.
  3. You get IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0 from inside HnswGraphBuilder. The IndexWriter closes as a tragic event, and the 1,000 uncommitted documents are lost.
  4. Why: the builder sizes its level-0 merge queue Math.min(beamWidth / 2, M * 3). Integer division makes 1 / 2 = 0, and the queue's heap rejects size 0. The two 500-vector segments flushed only because segments below 648 vectors skip the graph.
  5. A flush cannot get past that size either: the 648th vector document of a segment and the next one are rejected at addDocument. On 10.2.x and 10.3.x even the first vector document fails.

Who is affected

Only applications that set beamWidth = 1 on an HNSW vector format; the default is 100. Then any maxConn and any data fail on the first graph build, loudly. The last commit stays intact, but a merge failure closes the writer and discards uncommitted documents. Rare: no upstream test uses 1, and the recorded searches found no report.

Mechanism, terms and numbers600 words

The HNSW vector formats (Lucene99HnswVectorsFormat, and by code reading the scalar-quantized and bit-vector HNSW formats and two sandbox formats) accept any beamWidth from 1 to 3200, and HnswGraphBuilder rejects only beamWidth <= 0. Since 10.2.0 (#14331, "Speedup merging of HNSW graphs") every builder also allocates an extra candidate queue for merge-time level-0 searches, sized Math.min(beamWidth / 2, M * 3). For beamWidth = 1 that is 0, and the heap under the queue throws IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0. So no HNSW graph can be built with a beam width of 1, for any maxConn and any data. From 10.4.0, segments below the tiny-segment threshold (648 vectors by default) still flush without a graph; then the first merge that has to build one throws, and IndexWriter treats it as a tragic event: the writer closes and everything added since the last commit is discarded. On the flush path the 648th vector document of a segment is rejected, and so is the next one, while the writer stays open. On 10.2.x and 10.3.x the first vector document already fails. 10.1.0 and earlier work; every release from 10.2.0 to 10.6.0 and main fail. The fix is one line, Math.max(1, ...).

The code (main 645d4fe14a20)

  • Accepted range. Lucene99HnswVectorsFormat.java:278-283: if (beamWidth <= 0 || beamWidth > MAXIMUM_BEAM_WIDTH) throws "beamWidth must be positive and less than or equal to 3200" (:112). The same check in Lucene104HnswScalarQuantizedVectorsFormat.java:171-173, HnswBitVectorsFormat.java:116-118 and the sandbox DedupHnswVectorsFormat.java:175-180 and DedupHnswScalarQuantizedVectorsFormat.java:189-194 (both write through Lucene99HnswVectorsWriter). HnswGraphBuilder.java:177-179: "beamWidth must be positive".
  • Three queues. The base constructor (HnswGraphBuilder.java:166-196), which every other constructor and subclass reaches, allocates entryCandidates = new GraphBuilderKnnCollector(1) (:193), beamCandidates = new GraphBuilderKnnCollector(beamWidth) (:194) and beamCandidates0 = new GraphBuilderKnnCollector(Math.min(beamWidth / 2, M * 3)) (:195). GraphBuilderKnnCollector(int k) makes new NeighborQueue(k, false) (:821-822), which makes new TernaryLongHeap(initialSize) (NeighborQueue.java:59-60), which throws for initialCapacity < 1 (TernaryLongHeap.java:57-59).
  • Only merges that reuse graphs use the third queue (:349-351, level == 0 && eps0 != null && eps0.size() > 0, called from MergingHnswGraphBuilder.java:229 and, on main, from the concurrent merge's HnswConcurrentMergeBuilder.java:602), but it is allocated in every builder, so all graph building fails: the merge without a source graph (IncrementalHnswGraphMerger.java:159-161, the repro), the flush (Lucene99HnswVectorsWriter.java:832-836 -> :809-814), the merge of segments with graphs (MergingHnswGraphBuilder.java:73; by code reading) and the concurrent merge workers (HnswConcurrentMergeBuilder.java:493, :519; by code reading).
  • When a graph is needed. shouldCreateGraph(100, n) is n > (int) (ln(n) * 100) (Lucene99HnswVectorsWriter.java:716-722, HnswGraphSearcher.java:56-58, HNSW_GRAPH_THRESHOLD = 100 at Lucene99HnswVectorsFormat.java:137): false for 500 (621), true from 648 (647) and for 1,000 (690).
  • Why the flush keeps failing. In addValue the exception comes from initializeGraphBuilder() (:835) before node++ (:841), so the next vector document meets the same condition and throws again (the next one was executed). "Rejected" is what addDocument reports: the field writer has already buffered the vector (flatFieldVectorsWriter.addValue, :832), and DocumentsWriterPerThread marks the failed document deleted (DocumentsWriterPerThread.java:302-307, :399).
  • Why the writer closes. IndexWriter.merge catches the Throwable and calls tragicEvent(t, "merge") (IndexWriter.java:5109-5113), which closes the writer and rolls back uncommitted changes (tragicEvent -> maybeCloseOnTragicEvent -> rollbackInternal, :6090-6105). Only a failed merge does this; a flush-path failure leaves the writer open.
  • Exhaustive check. Over all 1,638,400 accepted (maxConn, beamWidth) pairs (1..512 x 1..3200), 512 give a non-positive queue size, all with beamWidth = 1; with the fix, none.
Exposure and likelihood302 words
  • Who is hit. Lucene99HnswVectorsFormat(maxConn, 1) (executed); by code reading also Lucene104HnswScalarQuantizedVectorsFormat, HnswBitVectorsFormat and the sandbox DedupHnswVectorsFormat / DedupHnswScalarQuantizedVectorsFormat, which accept the same range. Any maxConn, any data, any similarity.
  • What goes wrong (10.4.0 and later). Small segments flush; the first merge, forced or natural, whose result needs a graph throws, and the writer closes as a tragic event, which rolls it back: every document added since the last commit is lost (all 1,000 in the repro, which never commits; executed: afterwards the directory holds no index). A flush of 648 or more vectors rejects the threshold-crossing document and the next one at addDocument, while the writer stays open.
  • 10.2.x and 10.3.x build the flush graph eagerly, so the first vector addDocument already throws (LongHeap "maxSize must be > 0 ... got: 0").
  • Upgrades. An index written with beamWidth = 1 on 10.1.0 or earlier keeps working on 10.2.0 or later only until its first graph build: a merge that needs a graph, or a new vector document (on 10.2.x-10.3.x the first one, from 10.4.0 the one that takes a segment to 648 vectors). By code reading: beamWidth is not stored in the index.
  • What was not seen. Every executed failure is a loud exception; no silent corruption, hang or wrong search result was observed in the executed tests, and the last commit stayed readable (the independent reproduction's probe: 800 committed documents in 2 segments after the failed merge).
  • Workaround. Use beamWidth >= 2; 2 passes on main, 10.6.0 and 10.1.0.
  • How likely. Rare: upstream randomized tests draw beam widths from 5 or more (HnswGraphTestCase nextInt(10) + 5, RandomCodec 10 to 50), and the recorded searches found no report in the five affected release lines; whoever sets 1 hits it on the first graph build.

Minimal repro

One JUnit test (public Lucene APIs plus the test framework, no package-private member): Lucene99HnswVectorsFormat(16, 1), two flushed segments of 500 fixed 2-d vectors, forceMerge(1). One thread (SerialMergeScheduler); it failed in every recorded run, with assertions on and off, on main and 10.6.0, and passes on 10.1.0. With the fix it passes unchanged, so it doubles as the regression test.

TestHnswBeamWidthOneMerge.java (lucene/core, package org.apache.lucene.codecs.lucene99, 1 test)50 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.codecs.lucene99;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.KnnFloatVectorField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TestUtil;

public class TestHnswBeamWidthOneMerge extends LuceneTestCase {

  // Expected: forceMerge builds the HNSW graph with beamWidth = 1.
  // Actual: IllegalArgumentException "initialCapacity must be > 0 ... got: 0"; the writer closes.
  public void testForceMergeWithBeamWidthOne() throws Exception {
    IndexWriterConfig iwc =
        new IndexWriterConfig()
            .setCodec(TestUtil.alwaysKnnVectorsFormat(new Lucene99HnswVectorsFormat(16, 1)))
            .setMergeScheduler(new SerialMergeScheduler());
    try (Directory dir = newDirectory();
        IndexWriter w = new IndexWriter(dir, iwc)) {
      for (int i = 0; i < 1000; i++) {
        Document doc = new Document();
        doc.add(new KnnFloatVectorField("v", new float[] {i, i % 7}));
        w.addDocument(doc);
        if (i == 499) {
          w.flush();
        }
      }
      w.forceMerge(1);
    }
  }
}
Command
cp $REPRO/TestHnswBeamWidthOneMerge.java $LUCENE/lucene/core/src/test/org/apache/lucene/codecs/lucene99/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.codecs.lucene99.TestHnswBeamWidthOneMerge -Ptests.seed=DEADBEEF -Ptests.asserts=true
# and once more with -Ptests.asserts=false: it fails both ways
Expected
forceMerge(1) builds one segment with an HNSW graph, as with beamWidth = 2 and as on 10.1.0.
Observed
IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0 at HnswGraphBuilder.<init>:195, and the IndexWriter closes.

All files of this repro: repro/LFV-1056/

More files, environment, full output529 words

Copy the test into lucene/core/src/test/org/apache/lucene/codecs/lucene99/ of a Lucene checkout and run the command below; it uses public Lucene APIs plus Lucene's test framework (LuceneTestCase, newDirectory(), TestUtil.alwaysKnnVectorsFormat) and no package-private member. The vectors, the codec and the merge scheduler are fixed by the test; newDirectory() returns a randomised directory, and LuceneTestCase also randomises the similarity, the locale and the timezone. It failed under seed DEADBEEF with assertions on and off and under seed CAFEBABE with assertions off. The 500/500 split matters: each flushed segment stays below the 648-vector graph threshold, so the failure shows at the merge, where it closes the writer.

This is the hunt's minimal test with its one comment line rewritten to fit the 100-column format check; the code is unchanged. It passes :lucene:core:checkGoogleJavaFormat; the original 127-column comment did not. With assertions on and off it fails on main and passes with the fix, under gradle and the standalone runner; on the standalone runner it fails on 10.6.0 and passes on 10.1.0.

The test checks only that forceMerge(1) does not throw; an upstream regression test may also assert one segment and a kNN hit, as the graph-merge probe under Suggested fixes does. The registration check, the credibility audit and the diagram use another, unpublished copy of the same test: the field is named "vector", two lines carry comments, and it ends with assertTrue(w.isOpen()), which on main is never reached because forceMerge throws. It fails and passes the same way.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT): fails; also fails on the 10.2.0, 10.3.2, 10.4.0, 10.5.2 and 10.6.0 release jars; passes on 10.1.0
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (the only platform it ran on)
Command (environment)
cp $REPRO/TestHnswBeamWidthOneMerge.java $LUCENE/lucene/core/src/test/org/apache/lucene/codecs/lucene99/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.codecs.lucene99.TestHnswBeamWidthOneMerge -Ptests.seed=DEADBEEF -Ptests.asserts=true
# and once more with -Ptests.asserts=false: it fails both ways

Observed output

# gradle, fresh worktree at main 645d4fe14a20, -Ptests.seed=DEADBEEF -Ptests.asserts=true (the same with false)
TestHnswBeamWidthOneMerge > testForceMergeWithBeamWidthOne FAILED
    java.lang.IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0
        at __randomizedtesting.SeedInfo.seed([DEADBEEF:D330CEFBEFE7284]:0)
        at org.apache.lucene.util.TernaryLongHeap.<init>(TernaryLongHeap.java:59)
        at org.apache.lucene.util.hnsw.NeighborQueue.<init>(NeighborQueue.java:60)
        at org.apache.lucene.util.hnsw.HnswGraphBuilder$GraphBuilderKnnCollector.<init>(HnswGraphBuilder.java:822)
        at org.apache.lucene.util.hnsw.HnswGraphBuilder.<init>(HnswGraphBuilder.java:195)
        at org.apache.lucene.util.hnsw.HnswGraphBuilder.<init>(HnswGraphBuilder.java:147)
        at org.apache.lucene.util.hnsw.HnswGraphBuilder.<init>(HnswGraphBuilder.java:141)
        at org.apache.lucene.util.hnsw.HnswGraphBuilder.create(HnswGraphBuilder.java:123)
        at org.apache.lucene.util.hnsw.IncrementalHnswGraphMerger.createBuilder(IncrementalHnswGraphMerger.java:160)
        at org.apache.lucene.util.hnsw.IncrementalHnswGraphMerger.merge(IncrementalHnswGraphMerger.java:255)
        at org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsWriter.buildAndWriteGraph(Lucene99HnswVectorsWriter.java:525)
        ...
        at org.apache.lucene.index.IndexWriter.merge(IndexWriter.java:5081)
        ...
        at org.apache.lucene.index.IndexWriter.forceMerge(IndexWriter.java:2229)
1 test completed, 1 failed

# standalone runner, the same test (tests_run=1 failures=1 each, seed DEADBEEF)
main 645d4fe14a20                   -ea and -da   the same exception at HnswGraphBuilder.<init>:195
10.6.0 / 10.5.2 / 10.4.0            -da           the same, at :216 / :215 / :190
10.3.2 / 10.2.0                     -da           at the first addDocument: maxSize must be > 0 and < 2147483630; got: 0 (LongHeap), :165
10.1.0                              -ea and -da   passes

# tragic close with the default ConcurrentMergeScheduler (an independent probe on main, -da; 2 committed 400-vector segments)
PROBE bw=2 forceMerge ok / isOpen=true tragic=null / committed docs=800 segs=1
PROBE bw=1 forceMerge threw java.io.IOException: background merge hit exception: ... cause=java.lang.IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0
PROBE bw=1 isOpen=false tragic=java.lang.IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 0
PROBE bw=1 committed docs=800 segs=2
PROBE addDoc failed at doc 647: java.lang.IllegalArgumentException: initialCapacity must be > 0 ...   (0-based: the 648th)
PROBE addDoc isOpen=true tragic=null
PROBE next addDoc: java.lang.IllegalArgumentException: initialCapacity must be > 0 ...

Why it looks unintended

  • The formats and the builder state the rule as "beamWidth must be positive"; the limit tests reject only 0 and below.
  • It worked through 10.1.0, which has no such queue; within the #14331 PR, min(beamWidth, M * 2) became min(beamWidth / 2, M * 3).
  • The error is an internal heap check at merge time that closes the writer, not a validation.
The full argument234 words
  • Contract. The format javadoc calls beamWidth "the size of the queue maintained during graph construction" (Lucene99HnswVectorsFormat.java:191), the builder javadoc "the size of the beam search to use when finding nearest neighbors" (HnswGraphBuilder.java:133, :161). Nothing documents a minimum of 2 or says that half the beam sizes another queue; the merge builder's javadoc describes the merge-time level-0 search as "We also limit beamWidth (efConstruction to M*3)" (MergingHnswGraphBuilder.java:47-49), a cap, which for beamWidth = 1 would be 1, not 0.
  • Tests. TestLucene99HnswVectorsFormat.testLimits rejects beamWidth 0, -1 and 3201 only (:64-74); HnswGraphTestCase.testHnswGraphBuilderInvalid says // beamWidth must be > 0 (:888-890).
  • History. Inside the #14331 PR branch, commit 09735cf80a1 ("Adjust beamCandidates0", 2025-03-18) replaced Math.min(beamWidth, M * 2) with Math.min(beamWidth / 2, M * 3); the discussion covers only the cap (M * 2 to M * 3). The builder's beamWidth <= 0 check dates from 2020: it was added by 09f78e2927c (LUCENE-9610); git blame shows e1cd426bce39 (LUCENE-9644), which moved it.
  • A beam width below maxConn has been called valid. In #13752 (2024, closed as not planned) Michael McCandless asked whether beamWidth < maxConn should throw; Ben Trent answered "It is a weird configuration for sure, but I am not sure its invalid", and no validation was added.
  • A sibling edge case in the same merger was fixed as a bug: #14407 / #14420 (an empty join-set heap, fixed in 10.2.0).
The devil's-advocate check223 words

The case (built by the intended-behaviour devil's advocate and the cross-model reviews, all rating it weak): a beam of one gives a poor graph and nobody should run it in production; the formats are @lucene.experimental (Lucene99HnswVectorsFormat.java:83); a maintainer may prefer to reject beamWidth < 2.

Why it fails: the documented and tested rule is "positive", the value worked through 10.1.0, and today's failure is an opaque internal error that closes the writer at merge time, not a clear validation. @lucene.experimental allows API changes, not crashes on accepted input. Rejecting beamWidth < 2 is one of the fixes (option B), and it still changes code. The argument bears on priority (Low), not on whether it is a bug.

The hunt's no-noise rule does not claim behaviour that "needs absurd parameters and then fails loudly". This bug does fail loudly, and 1 is an unusual value: that is the main argument for Low priority, and it is conceded. It is still claimed because the value is inside the documented and tested contract ("positive"), it worked through 10.1.0 (a regression), and the failure does not come where the value is set: it comes later, at the first graph build, often at a merge, where it closes the writer and rolls back uncommitted documents. The cross-model fact check reached the same call: claim, Low.

Suggested fix

Recommended Clamp the level-0 merge queue to at least 1 (one line in HnswGraphBuilder)

Clamp the level-0 merge queue to at least one slot: Math.max(1, Math.min(beamWidth / 2, M * 3)) in the HnswGraphBuilder constructor. Nothing changes for beamWidth >= 2. The test, a graph-merge probe and the closest HNSW and codec suites (526 lucene/core tests and 8 bit-vector tests) pass.

beamCandidates0 = new GraphBuilderKnnCollector(Math.max(1, Math.min(beamWidth / 2, M * 3)));
// was: new GraphBuilderKnnCollector(Math.min(beamWidth / 2, M * 3))
All options and trade-offs3 options ยท 539 words

Three options; A was validated with gradle in a fresh scratch worktree at 645d4fe14a20, B and C were not executed. A maintainer may prefer B, a clear early rejection; either resolves the bug. Not a fix: allocating beamCandidates0 lazily, only when the eps0 path needs it. Flushes and graph-free merges would work, but the merge of two segments that have graphs would still hit the zero size.

Clamp the level-0 merge queue to at least 1 (one line in HnswGraphBuilder) (validated by the checks) (recommended)

HnswGraphBuilder.java:195 becomes beamCandidates0 = new GraphBuilderKnnCollector(Math.max(1, Math.min(beamWidth / 2, M * 3)));.

beamCandidates0 = new GraphBuilderKnnCollector(Math.max(1, Math.min(beamWidth / 2, M * 3)));
// was: new GraphBuilderKnnCollector(Math.min(beamWidth / 2, M * 3))

Trade-offs

  • No change for beamWidth >= 2: there min(beamWidth / 2, M * 3) is already at least 1 (M >= 1), so every graph built today is built the same way.
  • For beamWidth = 1 the level-0 merge search keeps 1 candidate, the same as the main beam queue.
  • Keeps the documented "positive" contract, and beamWidth = 1 works again as it did up to 10.1.0.

What the checks validated

Gradle in a fresh scratch worktree at 645d4fe14a20 (JDK 25.0.3, -Ptests.seed=DEADBEEF), 2026-10-30:

  • without the fix the minimal test fails with -Ptests.asserts=true and false; git apply of the one-line diff succeeds, and with it the test passes both ways; after git apply -R it fails again;
  • the closest suites pass with the fix: org.apache.lucene.util.hnsw.*, org.apache.lucene.codecs.lucene99.*, org.apache.lucene.codecs.lucene104.* and TestKnnGraph = 26 suites, 526 tests, 18 skipped, 0 failures (54 of them postings, segment-info and packing tests, and 1 the minimal test; among them TestHnswFloatVectorGraph 32, TestHnswByteVectorGraph 26, TestLucene99HnswVectorsFormat 60, TestLucene104HnswScalarQuantizedVectorsFormat 64); TestHnswBitVectorsFormat in lucene/codecs: 8 tests, 0 failures;
  • :lucene:core:checkGoogleJavaFormat passes with the fix and the test;
  • the graph-merge path, which by code reading uses the clamped queue (MergingHnswGraphBuilder.java:229 passes each smaller-graph node's known neighbours as eps0; the probe does not instrument the queue): a probe (not the repro) with two flushed 1,000-vector segments that each get a graph, then forceMerge(1), passes with the fix (-ea, -da): the HNSW info stream reports "build graph from merging 2 graphs of 2000 vectors, graph sizes:1000 1000", the merged index has 1 segment and 2,000 documents, and a k = 5 kNN query returns 5 hits;
  • earlier: the hunt's shadow-fix runs pass the test on main and on 10.2.0, and an independent reviewer from another model family ran the upstream format-limit, builder-invalid-input and graph read/write tests with the fix: pass.

Reject beamWidth < 2 with a clear message

In Lucene99HnswVectorsFormat, Lucene104HnswScalarQuantizedVectorsFormat, HnswBitVectorsFormat and HnswGraphBuilder. Not executed.

Trade-offs

Fails fast at codec construction instead of at a merge, but changes the documented "positive" contract and the testLimits / testHnswGraphBuilderInvalid expectations, rejects configurations that worked through 10.1.0, and must be repeated in every format with the same check (the sandbox dedup formats have it too).

Round up: Math.min((beamWidth + 1) / 2, M * 3)

Not executed. Never 0 either.

Trade-offs

Changes the queue size for every odd beamWidth below 6 * M (3 -> 2, 5 -> 3, ...), so merge-built graphs change for those settings; option A changes nothing outside beamWidth = 1.

Downstream note

By code reading only, saved snapshots suggest a user-chosen value of 1 can reach this crash downstream: Solr's DenseVectorField passes hnswEfConstruction to Lucene99HnswVectorsFormat, and the OpenSearch k-NN plugin's Lucene HNSW method accepts any ef_construction > 0. Their revisions and the OpenSearch mapping to beamWidth were not confirmed.

How it was found

Two independent hunts found it in the same minute, by reading the HNSW graph builder's constructor while deriving the parameter domain of their models; one hunt's bounded parameter sweep then hit the exception at beamWidth = 1 for every maxConn. A later hunt reduced it to one test and checked every accepted parameter pair.

The method in full223 words

Method: code reading during model building, then executed tests and an exhaustive check (the hunt's non-formal tier: the bug was seen in the code, not reported by a model checker).

  • First signal (2026-10-25). A unit modelling the HNSW graph format derived the legal parameter domain from the format's validation (1 <= beamWidth <= 3200) and checked that every buffer sized from maxConn and beamWidth is legal on it; beamWidth / 2 is not. It confirmed this with an executed probe and a JUnit on the stock main jars. In the same minute a unit modelling merge-time graph reuse noticed the line while modelling the eps0 path, and its bounded-exhaustive harness reported the builder exception for beamWidth = 1 at every M and for no other beam width.
  • Hunt. An adjudication-take-over unit re-ran the earliest repro on main (-ea, -da: fails), reduced it to one JUnit test with no package-private member, checked the three queue sizes over all 1,638,400 accepted (maxConn, beamWidth) pairs (512 non-positive, all at beamWidth = 1; 0 with the fix) and wrote the one-line fix.
  • Checks. An independent reproducer, an intended-behaviour devil's advocate, two executed reviews by an independent reviewer from another model family (OpenAI GPT-5.6), an adjudication, a credibility audit and a no-noise gate confirmed it; the version-history runs ran the release matrix.

Prior reports, history and checks

Prior reports

No earlier report found (searched 2026-10-30, re-checked the same day). Closest: #14331, the introducing PR, whose discussion covers only the queue's upper cap, and #13752, where a beam width below maxConn was called a valid configuration.

Searches4 searches

Related, not this bug: #14331 (the introducing PR) and #14380 (its backport); #14407 / #14420 (a different empty-heap edge case in the same merger, fixed in 10.2.0); #13752 (2024, closed as not planned: a beamWidth below maxConn called valid, no validation added); #13399 and #13525 (open, stale: move the format's validation, same bounds); #11387 and an Elasticsearch PR with the same "maxSize must be > 0" family of message from unrelated search code.

What was searched, when, and what turned up
WhereWhenHowFound
Search tool, two runs (GitHub issues, PRs and commits incl. the migrated JIRA archive; dev@, java-user@, issues@, commits@; Elasticsearch, OpenSearch, OpenSearch k-NN, Solr; web)2026-10-30112 queries: class, field and message terms (beamCandidates0, "beamWidth / 2", GraphBuilderKnnCollector, "initialCapacity must be > 0", "maxSize must be > 0", "beamWidth must be positive", efConstruction minimum); 269 distinct hits, every plausible one openedno report of this bug
GitHub (apache/lucene and all of GitHub)2026-10-3018 own searches (beamWidth=1, "beam width 1", efConstruction=1, the messages); "beamCandidates0" anywhere on GitHubonly #14331
Open and recent apache/lucene PRs2026-10-30the changed files of all 409 open PRs (16 touch the fix-site files, their diffs grepped) and the 49 PRs updated since 2026-10-28none changes beamCandidates0, beamWidth / 2 or the bounds
GitHub, re-check for this doc2026-10-30 (saved)read-only gh calls, commands and output saved: issues updated since 2026-10-29 matching beamWidth; issues and PRs matching beamCandidates0; issues and PRs quoting "initialCapacity must be"; the 15 PRs updated since 2026-10-30, their changed files and HnswGraphBuilder.java patches; upstream main head and the file's newest commitsnothing new: only #16906 (unrelated) and #14331; #16806 changes HnswGraphBuilder.java but not the line; upstream main 2f424fd428da still has it at :195

History

Introduced in 10.2.0 by #14331 (3752dba9c51, merged 2025-03-20; backport aa1e9f34b37). 9.12.3, 10.0.0 and 10.1.0 pass; 10.2.0, 10.3.2, 10.4.0, 10.5.2, 10.6.0 and main fail; 10.2.0 with only the one-line fix passes. Not fixed upstream.

Versions tested7 rows

Introduced by #14331 "Speedup merging of HNSW graphs" (PR by Mayya Sharipova; CHANGES credits Mayya Sharipova and Tom Veasey), squash commit 3752dba9c51 on main and backport aa1e9f34b37 (#14380) the same day; git blame puts line 195 on it, and git log -S beamCandidates0 and -G 'beamWidth / 2' find only it. Inside the PR, 09735cf80a1 (2025-03-18) changed the size from Math.min(beamWidth, M * 2) to Math.min(beamWidth / 2, M * 3). The failure point moved in 10.4.0, when #14963 let tiny segments skip the graph: from the first vector addDocument to the first segment or merge that needs a graph. Read only: 10.2.1, 10.2.2, 10.3.0, 10.3.1, 10.5.0 and 10.5.1 contain the same line; all 9.x lack it. Upstream main 2f424fd428da (2026-10-30) still has it at :195.

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedthe minimal test fails (gradle and the standalone runner, -ea and -da) at :195
10.6.0, 10.5.2, 10.4.0 (release jars) affectedfails at forceMerge: "initialCapacity must be > 0 ... got: 0" (:216, :215, :190)
10.3.2, 10.2.0 (release jars) earlier failurefails at the first vector addDocument: LongHeap "maxSize must be > 0 ... got: 0" (:165)
10.2.0 + the one-line fix not affectedpasses
10.1.0 not affectedpasses (-ea, -da); no beamCandidates0
10.0.0, 9.12.3 not affecteda port of the test passes
fix A (scratch worktree at 645d4fe14a20) not affectedthe minimal test, the closest HNSW and codec suites (526 lucene/core tests, 54 of them postings, segment-info and packing tests) and 8 bit-vector tests in lucene/codecs pass

Independent checks

An independent reproducer, an intended-behaviour devil's advocate, two executed cross-model reviews, an adjudication, a credibility audit and a no-noise gate confirm it; the version-history runs ran the release matrix. This doc re-ran the test and validated the fix with gradle.

Checks and verdicts470 words

The cross-model reviews ran on main ea57360912ed, where HnswGraphBuilder.java, NeighborQueue.java, TernaryLongHeap.java and Lucene99HnswVectorsFormat.java are identical to 645d4fe14a20; other call-path files changed in between (#16794), and the test fails at the same line on both.

Each independent check and its verdict
CheckVerdictWhat it found
first finders (two hunt units, 2026-10-25) candidateCode reading of the builder constructor, a probe and a JUnit on the main jars; a bounded parameter sweep hit the exception at beamWidth = 1 for every M.
finder (judge-take-over unit) candidateRe-ran the earliest repro, reduced it to one JUnit, checked all 1,638,400 accepted pairs, wrote the fix.
cross-model review, refute lens (OpenAI GPT-5.6, executed) real (high)Own codec test ((4, 1), 700 vectors, commit and reopen) fails on the flush path with -ea and -da; the beamWidth = 2 control reads back 700 documents; passes with the fix.
independent reproducer realOwn 2-test JUnit fails 2 of 2 on main (-ea, -da) on the merge and flush paths; fails on 10.6.0, 10.5.2 and 10.2.0, passes on 10.1.0 and 9.12.3; tragic-close probe.
cross-model review, intended lens (OpenAI GPT-5.6, executed) real (high)Own test fails on main, passes on 10.1.0 and with the fix; upstream limit, invalid-input and graph tests pass with the fix.
intended-behaviour devil's advocate real (high)No javadoc, CHANGES, MIGRATE, test or #14331 discussion restricts small beam widths; a core-only program fails on 10.6.0 and main and passes on 10.1.0; beamWidth = 2 passes on all three.
adjudication confirmed (high); crash; LowRe-ran the finder's test, the reproducer's test and the fix check; shipped the SerialMergeScheduler version.
Registration check, credibility audit, no-noise gate keep; claimTheir copy of the test (an unpublished variant of the Minimal repro test, described there) fails on main (DEADBEEF -ea and -da, CAFEBABE -da) and passes with the fix.
the version-history runs regression in 10.2.0Release matrix from 9.12.3 to 10.6.0; 10.2.0 plus the fix passes.
this doc reproduced, fix validatedGradle in a fresh worktree without and with the fix (assertions on and off) and the closest suites; the format-clean test on main, 10.6.0 and 10.1.0; a graph-merge probe with the fix.
independent fact check of this doc real; 3 text errors, fixedRan the test with the standalone runner on main (DEADBEEF -ea, -da; CAFEBABE -da), 10.6.0, 10.1.0 and with the fix, and the doc's own gradle command in a fresh worktree without and with the fix; a SerialMergeScheduler probe showed the writer closed and no index on disk.
cross-model fact check of this doc (OpenAI GPT-5.6) claim, LowA read-only review of part of the doc's evidence; its wording corrections are in this version.
diagram check pass

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,488 words

Finding record

LFV-1056, snapshot of 2026-11-06

Registry title
With beamWidth = 1, which the HNSW formats accept, every HnswGraphBuilder sizes its level-0 merge queue min(beamWidth / 2, M * 3) = 0 and throws, so building any HNSW graph fails and a merge closes the IndexWriter (since 10.2.0)
Area
core / HNSW graph construction (util/hnsw HnswGraphBuilder constructor; codecs/lucene99 Lucene99HnswVectorsFormat beamWidth range)
Priority
Low, rank 686: BeamWidth = 1 is accepted by the core HNSW formats but makes every graph build throw, so a merge closes the IndexWriter (released since 10.2.0), the same accepted-setting crash class as LFV-589.
Impact class
crash
Impact note
Hits only applications whose vector field uses an HNSW format built with beamWidth = 1 (Lucene99HnswVectorsFormat(maxConn, 1) [exec]; by code reading also Lucene104HnswScalarQuantizedVectorsFormat and HnswBitVectorsFormat, which accept the same 1..3200 range), for any maxConn, data and scale: from 10.4.0 segments below the tiny-segment threshold (about 650 vectors with the defaults) flush without a graph, then the first merge that has to build one throws IllegalArgumentException and closes the IndexWriter as a tragic event, which discards every document added since the last commit (the last commit stays intact), and on the flush path the vector document that pushes a segment over the threshold and every later one in that segment are rejected; on 10.2.x-10.3.x the first vector document already fails; -ea and -da, released 10.2.0 through 10.6.0.
Summary
You configure a vector field with an HNSW format and beamWidth = 1, the smallest value the format and the graph builder accept ('beamWidth must be positive'), index vectors and merge, and you expect it to work as it did up to 10.1.0, only with a poorer graph. Instead every HnswGraphBuilder constructor sizes its level-0 merge candidate queue as Math.min(beamWidth / 2, M * 3), which integer division makes 0, and the heap rejects capacity 0 ('initialCapacity must be > 0 ... got: 0'), so no HNSW graph can be built at all. Small segments still flush without a graph, then the first merge that needs one throws and closes the IndexWriter, and the vector document that would push a segment past the graph threshold is rejected.
Introduced
3752dba9c51d817363c76e9a388cd68dcba5c4a0 (main) and its branch_10x backport aa1e9f34b373882025726a01982b768e4948ccf4 (#14380), first shipped in 10.2.0: apache/lucene#14331 'Speedup merging of HNSW graphs' (author mayya-sharipova, merged 2025-03-20). The 'beamWidth / 2' sizing entered the PR branch in 09735cf80a1 ('Adjust beamCandidates0', 2025-03-18), replacing Math.min(beamWidth, M * 2), which is at least 1 for every accepted value [read: blame of HnswGraphBuilder.java:195, git log -S beamCandidates0 and -G 'beamWidth / 2' in the full-history clone find only this commit; exec at release granularity: 10.1.0 passes, 10.2.0 fails, 10.2.0 with only the one-line fix shadowed in passes] (2025-03-20 (author date 2025-03-20T10:02:50-04:00 = 14:02:50Z, squash-merged to main the same minute; branch_10x backport merged 2025-03-20T18:43:08Z))
First affected version
10.2.0 [exec: 10.2.0 fails with -ea and -da at the first vector addDocument; 10.1.0 passes with -ea and -da; 10.0.0 and 9.12.3 pass with a port of the test]
Affected versions
every release from 10.2.0 through 10.6.0 (10.2.0-10.2.2, 10.3.0-10.3.2, 10.4.0, 10.5.0-10.5.2, 10.6.0) and main [exec: 10.2.0 (-ea, -da), 10.3.2 (-da), 10.4.0 (-da), 10.5.2 (-ea, -da), 10.6.0 = 4ae946e5a9b on the Maven Central jars (-ea, -da), main 645d4fe14a20 (-ea, -da; re-run at the registration check 2026-10-30: 1 of 1 fails each, seed DEADBEEF); read: 10.2.1, 10.2.2, 10.3.0, 10.3.1, 10.5.0 and 10.5.1 contain the same line]. The failure point moves with the version: in 10.2.x-10.3.x the flush writer builds the graph eagerly, so the first vector addDocument throws ('maxSize must be > 0' from LongHeap at HnswGraphBuilder.<init>:165); from 10.4.0 (#14963, tiny segments skip the graph) small segments flush and the failure moves to the first segment or merge that needs a graph ('initialCapacity must be > 0' from TernaryLongHeap; 10.4.0 at <init>:190, 10.5.2 at :215, 10.6.0 at :216, main at :195). Not affected: 10.1.0, 10.0.0 and 9.12.3 [exec] and all 9.x (no beamCandidates0) [read]. Not fixed upstream: main head 716d6f216f5 (2026-10-29) still has the line at HnswGraphBuilder.java:195 and branch_10_6 at :216, and no open PR changes it [read, 2026-10-30]. Lucene104HnswScalarQuantizedVectorsFormat and HnswBitVectorsFormat reach the same builder [read only, not executed].
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
non-formal: code reading; also reached by exhaustive check
Upstream and prior reports
none found (2026-10-30, read-only; the prior-report record, verdict new: two scripted prior-report searches with 112 queries over GitHub issues and PRs, commits, downstream projects (Elasticsearch, OpenSearch, OpenSearch k-NN, Solr), the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and the web (269 distinct hits, 0 high overlap, every plausible hit opened), 18 own GitHub searches, the changed files of all 409 open apache/lucene PRs (16 touch the fix-site files; none of their diffs changes beamCandidates0, 'beamWidth / 2' or the beamWidth bounds) and the 49 PRs updated since 2026-10-28; refresh 2026-10-30: no new item, the PRs and issues updated since 2026-10-29 that mention beamWidth or HnswGraphBuilder (#16806, #16915, #16906) do not change the line, open #16871 does not touch HnswGraphBuilder, and upstream main head 716d6f216f5 still has it at HnswGraphBuilder.java:195). Closest items, none the same bug: #14331 (introducing PR; its discussion covers only raising the cap from M * 2 to M * 3) and its backport #14380, #14407 / #14420 (a different empty-heap edge case in the same merger, fixed in 10.2.0), #13752 (2024, closed as not planned: benwtrent called a small-beamWidth configuration 'weird ... but I am not sure its invalid' and no validation was added).
Lucene commit tested
main 645d4fe14a20 (11.0.0-SNAPSHOT), 2026-10-30: the minimal test with gradle in a fresh scratch worktree (fails with assertions on and off; with the one-line fix it passes, together with the closest HNSW and codec suites (526 lucene/core tests, among them 54 postings, segment-info and packing tests and the minimal test, plus 8 bit-vector tests) and the core format check) and with the standalone runner (-ea, -da); a graph-merge probe with the fix shadowed (-ea, -da); the release jars 10.6.0 and 10.1.0 (this doc's runs), and in the hunt 9.12.3, 10.0.0 (a port), 10.1.0, 10.2.0, 10.3.2, 10.4.0, 10.5.2 and 10.6.0, plus 10.2.0 with only the one-line fix. JDK 25.0.3, Linux aarch64. The cross-model reviews ran on main ea57360912ed, where HnswGraphBuilder.java and the heap and format files are identical (other call-path files changed in #16794; the test fails at the same line on both).

Lucene and upstream sources

Finding record
  • lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java:177-179 (at 645d4fe14a20) (the builder rejects only beamWidth <= 0: 'beamWidth must be positive'), :195 (beamCandidates0 = new GraphBuilderKnnCollector(Math.min(beamWidth / 2, M * 3)), allocated in every constructor), :349-351 (beamCandidates0 is used only when eps0 is non-empty, the level-0 search of merge-time graph reuse), :818-822 (GraphBuilderKnnCollector(int k) -> new NeighborQueue(k, false))
  • lucene/core/src/java/org/apache/lucene/util/hnsw/NeighborQueue.java:59-60 (at 645d4fe14a20) (new TernaryLongHeap(initialSize)) and lucene/core/src/java/org/apache/lucene/util/TernaryLongHeap.java:57-59 (initialCapacity < 1 -> IllegalArgumentException 'initialCapacity must be > 0 and < ...')
  • lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsFormat.java:83 (at 645d4fe14a20) (@lucene.experimental), :137 (HNSW_GRAPH_THRESHOLD = 100), :191-193 ('@param beamWidth the size of the queue maintained during graph construction.', public 2-argument constructor), :278-283 ('beamWidth must be positive and less than or equal to ' MAXIMUM_BEAM_WIDTH = 3200); lucene/core/src/java/org/apache/lucene/codecs/lucene104/Lucene104HnswScalarQuantizedVectorsFormat.java:171 and lucene/codecs/src/java/org/apache/lucene/codecs/bitvectors/HnswBitVectorsFormat.java:116 (same range check)
  • lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java:809-814 (at 645d4fe14a20) (flush path: initializeGraphBuilder -> HnswGraphBuilder.create) and lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java:158-161 (merge path: createBuilder -> HnswGraphBuilder.create)
  • lucene/core/src/java/org/apache/lucene/index/IndexWriter.java:5112 (at 645d4fe14a20) (IndexWriter.merge: tragicEvent(t, "merge"), which closes the writer and rolls back uncommitted changes)
  • lucene/core/src/test/org/apache/lucene/codecs/lucene99/TestLucene99HnswVectorsFormat.java:67-68 (at 645d4fe14a20) (testLimits rejects beamWidth 0 and -1 only) and lucene/core/src/test/org/apache/lucene/util/hnsw/HnswGraphTestCase.java:156 and :325 (random beamWidth nextInt(10) + 5)
The finding in 30 seconds
  • lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java:52, (at 645d4fe14a20) :57 (defaults 16 and 100), :166-196 (base constructor), :177-179, :193-195, :349-351, :821-822
  • lucene/core/src/java/org/apache/lucene/util/hnsw/NeighborQueue.java:59-60; (at 645d4fe14a20) lucene/core/src/java/org/apache/lucene/util/TernaryLongHeap.java:56-62
  • lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsFormat.java:112, (at 645d4fe14a20) :137, :278-283; lucene/core/src/java/org/apache/lucene/codecs/lucene104/Lucene104HnswScalarQuantizedVectorsFormat.java:171-173; (at 645d4fe14a20) lucene/codecs/src/java/org/apache/lucene/codecs/bitvectors/HnswBitVectorsFormat.java:116-118; (at 645d4fe14a20) lucene/sandbox/src/java/org/apache/lucene/sandbox/codecs/dedup/DedupHnswVectorsFormat.java:175-180, (at 645d4fe14a20) :201; DedupHnswScalarQuantizedVectorsFormat.java:189-194, :215
  • lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java:716-722, (at 645d4fe14a20) :809-816, :832-841; lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphSearcher.java:56-58 (at 645d4fe14a20)
  • lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java:157-177; (at 645d4fe14a20) MergingHnswGraphBuilder.java:73, :193-231; HnswConcurrentMergeBuilder.java:493, :519; HnswConcurrentMergeBuilder.java:582-602 (addJoinedNode -> addGraphNode(node, eps))
  • lucene/core/src/java/org/apache/lucene/index/IndexWriter.java:5109-5113, (at 645d4fe14a20) :6090-6105; lucene/core/src/java/org/apache/lucene/index/DocumentsWriterPerThread.java:302-307, :399-414
Who is affected
  • lucene/core/src/test/org/apache/lucene/util/hnsw/HnswGraphTestCase.java:156, (at 645d4fe14a20) :325; lucene/test-framework/src/java/org/apache/lucene/tests/index/RandomCodec.java:271-294
Why it looks unintended
  • lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsFormat.java:83, (at 645d4fe14a20) :191, :278-283; lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java:133, (at 645d4fe14a20) :161, :177-179; lucene/core/src/java/org/apache/lucene/util/hnsw/MergingHnswGraphBuilder.java:47-49
  • lucene/core/src/test/org/apache/lucene/codecs/lucene99/TestLucene99HnswVectorsFormat.java:64-74; (at 645d4fe14a20) lucene/core/src/test/org/apache/lucene/util/hnsw/HnswGraphTestCase.java:883-891
  • github.com/apache/lucene/issues/13752 (comments by mikemccand and benwtrent, read 2026-10-30 with gh)
History
  • github.com/apache/lucene/pull/14331/commits (09735cf80a1), read 2026-10-30 with gh api

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Made in parallel with this doc from the code at 645d4fe14a20, the minimal repro and an executed probe on the stock main jars (-ea and -da, and with the fix). The code panel of step 1 shows the registration check's copy of the minimal test (field "vector", two comments and a closing assertTrue(w.isOpen())); see Minimal repro.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.