weaviate/weaviate · error · ErrIllegalArguments

%w: discriminant size %d, expected %d for level %d

Error message

%w: discriminant size %d, expected %d for level %d

What it means

HashTree.Level() expects a level-local discriminant whose Size() exactly equals the number of nodes at that level (nodesAtLevel(level)). Bit i selects node i, so a wrong size means bit indices are ambiguous. The library throws ErrIllegalArguments to prevent misaligned selection masks.

Source

Thrown at usecases/replica/hashtree/hashtree.go:215

func (ht *HashTree) Level(level int, discriminant *Bitset, digests []Digest) (n int, err error) {
	ht.mux.Lock()
	defer ht.mux.Unlock()

	if level < 0 {
		return 0, fmt.Errorf("%w: invalid level(%d)", ErrIllegalArguments, level)
	}

	if level > ht.Height() {
		return 0, fmt.Errorf("%w: level(%d) is too high for current height(%d)", ErrIllegalState, level, ht.height)
	}

	if discriminant == nil {
		return 0, fmt.Errorf("%w: nil discriminant provided", ErrIllegalArguments)
	}

	expectedSize := nodesAtLevel(level)
	if discriminant.Size() != expectedSize {
		return 0, fmt.Errorf("%w: discriminant size %d, expected %d for level %d",
			ErrIllegalArguments, discriminant.Size(), expectedSize, level)
	}

	// one digest is written per set bit, so SetCount() capacity suffices (see LevelDiff)
	if len(digests) < discriminant.SetCount() {
		return 0, fmt.Errorf("%w: output buffer has not enough capacity", ErrIllegalArguments)
	}

	ht.sync()

	offset := InnerNodesCount(level)

	for i := 0; i < expectedSize; i++ {
		if discriminant.IsSet(i) {
			// bound writes even if the cached set count understates the bits
			if n == len(digests) {
				return 0, fmt.Errorf("%w: discriminant set count understates its set bits", ErrIllegalArguments)
			}

View on GitHub (pinned to 75aa4b6d11)

Solutions

  1. Build the discriminant with exactly NewBitset(nodesAtLevel(level)) bits for the level you query.
  2. If you only have a leaf-level discriminant, derive the per-level mask yourself, or use Diff/NewRangeReader which handle level-local masks internally.
  3. Verify both trees involved have identical Height() before sharing bitsets between them.

Example fix

// before
disc := hashtree.NewBitset(ht.LeavesCount(height)) // leaf-level size
ht.Level(l, disc, digests)
// after
disc := hashtree.NewBitset(nodesAtLevel(l)) // level-local size
ht.Level(l, disc, digests)
Defensive patterns

Strategy: validation

Validate before calling

if disc != nil && disc.Size() != nodesAtLevel(level) {
    return fmt.Errorf("discriminant size %d != %d for level %d", disc.Size(), nodesAtLevel(level), level)
}

Type guard

func isLevelLocalDisc(disc *hashtree.Bitset, level int) bool {
    return disc != nil && disc.Size() == nodesAtLevel(level)
}

Prevention

When it happens

Trigger: Passing a leaf-level discriminant (Size()==LeavesCount(height)) into Level for an inner level, or vice versa; reusing a walk bitset from a different tree height; calling Level on trees of different heights with the same Bitset.

Common situations: Using a discriminant produced by Diff (leaf-level) directly with Level(level,...) for level < Height(); caching bitsets across trees whose heights changed after a schema/shard change; confusion between LeavesCount and nodesAtLevel sizes.

Related errors


AI-assisted analysis of weaviate/weaviate@75aa4b6d11 (2026-09-04). Data as JSON: /api/errors/d8a66319e9bd90d8. Report an issue: GitHub.