#12959·weaviate

[HNSW] AddMulti panics on nil multivector input

Author: leemeiiCreated Sep 7, 2026Updated Sep 8, 2026
Labelsbugcommunity

How to reproduce this bug?

Save the following file as repro.py in a clean Weaviate source checkout:

python
#!/usr/bin/env python3

import os
import subprocess
from pathlib import Path

HELPER = r'''
package hnsw

import (
    "context"
    "fmt"
    "testing"
)

func TestAuditNilMultiVector(t *testing.T) {
    idx := createEmptyMultiVectorHnswIndexForTests(t, func(context.Context, uint64) ([][]float32, error) {
        return nil, nil
    })
    defer idx.Shutdown(context.Background())

    err := idx.AddMulti(context.Background(), 1, nil)
    fmt.Printf("target nil_multivector err=%v\n", err)
}

func TestAuditEmptyInnerVector(t *testing.T) {
    idx := createEmptyMultiVectorHnswIndexForTests(t, func(context.Context, uint64) ([][]float32, error) {
        return nil, nil
    })
    defer idx.Shutdown(context.Background())

    err := idx.AddMulti(context.Background(), 1, [][]float32{{}})
    fmt.Printf("control empty_inner_multivector err=%v\n", err)
}
'''

def run(go, source, test_name):
    return subprocess.run(
        [
            go, "test", "./adapters/repos/db/vector/hnsw",
            "-run", f"^{test_name}$", "-count=1", "-v",
        ],
        cwd=source,
        text=True,
        capture_output=True,
        timeout=120,
    )

def main():
    source = Path(os.environ.get("WEAVIATE_SRC", ".")).resolve()
    go = os.environ.get("GO_BIN", "go")
    helper = source / "adapters/repos/db/vector/hnsw/audit_empty_multivector_test.go"
    helper.write_text(HELPER, encoding="utf-8")

    try:
        passed = True

        for trial in range(10):
            control = run(go, source, "TestAuditEmptyInnerVector")
            target = run(go, source, "TestAuditNilMultiVector")

            control_output = control.stdout + control.stderr
            target_output = target.stdout + target.stderr

            control_ok = (
                control.returncode == 0
                and "control empty_inner_multivector" in control_output
                and "panic:" not in control_output
            )

            target_ok = (
                target.returncode != 0
                and "panic:" in target_output
                and "runtime error: index out of range [0] with length 0" in target_output
                and "AddMultiBatch.func2" in target_output
                and "insert.go:316" in target_output
            )

            ok = control_ok and target_ok
            passed &= ok
            print(
                f"trial={trial + 1} "
                f"control={control.returncode} "
                f"target={target.returncode} "
                f"oracle={ok}"
            )

        print(
            "BUG REPRODUCED: nil multivector causes HNSW AddMulti panic"
            if passed else
            "NO BUG OBSERVED OR INCONCLUSIVE"
        )
        return 0 if passed else 10

    finally:
        helper.unlink(missing_ok=True)

if __name__ == "__main__":
    raise SystemExit(main())

### What is the expected behavior?

A nil or empty multivector is invalid input, but `AddMulti` should return a controlled error instead of panicking.

Expected behavior:
  • return an error;
  • do not access a missing inner vector;
  • keep the Go process alive;
  • leave the HNSW index state unchanged.
The adjacent input `[][]float32{{}}` already returns a controlled error without panicking.


### What is the actual behavior?

Calling:

```go
idx.AddMulti(context.Background(), 1, nil)

terminates the test process with:

panic: runtime error: index out of range [0] with length 0

The stack trace reaches:

AddMultiBatch.func2
adapters/repos/db/vector/hnsw/insert.go:316

The process exits with a non-zero status instead of returning an error.

Supporting information

No response

Server Version

1.40.0-dev

Weaviate Setup

Single Node

Nodes count

No response

Code of Conduct