opentofu/opentofu · error

cannot search an empty list

Error message

cannot search an empty list

What it means

Thrown by IndexFunc's Impl in internal/lang/funcs/collection.go. The first argument passed the list-or-tuple type check, is fully known, but LengthInt() == 0, so there is nothing to search. This 'easy path' short-circuits before element iteration.

Source

Thrown at internal/lang/funcs/collection.go:204

		},
		{
			Name: "value",
			Type: cty.DynamicPseudoType,
		},
	},
	Type:         function.StaticReturnType(cty.Number),
	RefineResult: refineNotNull,
	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
		if !args[0].Type().IsListType() && !args[0].Type().IsTupleType() {
			return cty.NilVal, errors.New("argument must be a list or tuple")
		}

		if !args[0].IsKnown() {
			return cty.UnknownVal(cty.Number), nil
		}

		if args[0].LengthInt() == 0 { // Easy path
			return cty.NilVal, errors.New("cannot search an empty list")
		}

		for it := args[0].ElementIterator(); it.Next(); {
			i, v := it.Element()
			eq, err := stdlib.Equal(v, args[1])
			if err != nil {
				return cty.NilVal, err
			}
			if !eq.IsKnown() {
				return cty.UnknownVal(cty.Number), nil
			}
			if eq.True() {
				return i, nil
			}
		}
		return cty.NilVal, errors.New("item not found")

	},

View on GitHub (pinned to 3561785c48)

Solutions

  1. Guard the call: length(var.list) > 0 ? index(var.list, v) : -1 (or another sentinel you handle).
  2. Use contains() first if absence is normal: contains(var.list, v) ? index(var.list, v) : null.
  3. Fix the data source or variable so the list is genuinely populated before index() runs.
  4. Wrap with try(index(var.list, v), null) when emptiness is acceptable.

Example fix

# before
idx = index(var.availability_zones, var.az)

# after
idx = length(var.availability_zones) > 0 ? index(var.availability_zones, var.az) : -1
Defensive patterns

Strategy: validation

Validate before calling

# Check for content before searching
idx = length(var.list) > 0 ? index(var.list, v) : -1

Try / catch

try(index(var.list, v), -1) covers both the empty-list and not-found errors in one guard.

Prevention

When it happens

Trigger: index([], "x") literally, or index(var.list, v) where var.list evaluated to an empty list at plan/apply time. Unknown lists return unknown earlier, so only known empty collections reach this branch.

Common situations: A module receives an optional list that is empty in this deployment but index() is called unconditionally; a for_each/count data source produced zero items; a default empty list with no guard before positional search.

Related errors


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/7a6fec7ffec2a42c. Report an issue: GitHub.