deepset-ai/haystack · error · ValueError
header_split_levels must be a non-empty list.
Error message
header_split_levels must be a non-empty list.
What it means
MarkdownHeaderSplitter's __init__ validates header_split_levels. If it is not a list or is an empty list, a ValueError is thrown because the splitter needs at least one header level (1-6) on which to split markdown documents.
Source
Thrown at haystack/components/preprocessors/markdown_header_splitter.py:62
Defaults to True.
:param header_split_levels: List of header levels (1–6) to split on. For example, `[1, 2]` splits only
on `#` and `##` headers, merging content under deeper headers into the preceding chunk. Defaults to
all levels `[1, 2, 3, 4, 5, 6]`.
:param secondary_split: Optional secondary split condition after header splitting.
Options are None, "word", "passage", "period", "line". Defaults to None.
:param split_length: The maximum number of units in each split when using secondary splitting. Defaults to 200.
:param split_overlap: The number of overlapping units for each split when using secondary splitting.
Defaults to 0.
:param split_threshold: The minimum number of units per split when using secondary splitting. Defaults to 0.
:param skip_empty_documents: Choose whether to skip documents with empty content. Default is True.
Set to False when downstream components in the Pipeline (like LLMDocumentContentExtractor) can extract text
from non-textual documents.
"""
if header_split_levels is None:
header_split_levels = [1, 2, 3, 4, 5, 6]
if not isinstance(header_split_levels, list) or len(header_split_levels) == 0:
raise ValueError("header_split_levels must be a non-empty list.")
invalid = [lvl for lvl in header_split_levels if not isinstance(lvl, int) or lvl < 1 or lvl > 6]
if invalid:
raise ValueError(
f"header_split_levels contains invalid values: {invalid}. All levels must be integers between 1 and 6."
)
if len(header_split_levels) != len(set(header_split_levels)):
raise ValueError("header_split_levels must not contain duplicate values.")
self.page_break_character = page_break_character
self.secondary_split = secondary_split
self.split_length = split_length
self.split_overlap = split_overlap
self.split_threshold = split_threshold
self.skip_empty_documents = skip_empty_documents
self.keep_headers = keep_headers
self.header_split_levels = header_split_levels
self._header_split_levels_set = set(header_split_levels)
self._header_pattern = re.compile(r"(?m)^(#{1,6}) (.+)$") # ATX-style .md-headersView on GitHub (pinned to e318778c9b)
Solutions
- Pass a non-empty list of integers, e.g. header_split_levels=[1, 2, 3].
- Omit the parameter entirely to use the default [1, 2, 3, 4, 5, 6].
- Wrap a scalar level in a list: header_split_levels=[2] not header_split_levels=2.
- Ensure dynamic/config-driven values are validated for non-emptiness before constructing the component.
Example fix
// before MarkdownHeaderSplitter(header_split_levels=2) MarkdownHeaderSplitter(header_split_levels=[]) // after MarkdownHeaderSplitter(header_split_levels=[1, 2]) MarkdownHeaderSplitter() # defaults to [1,2,3,4,5,6]
Defensive patterns
Strategy: validation
Validate before calling
def valid_split_levels(v):
return isinstance(v, list) and len(v) > 0 and all(isinstance(l, int) and 1 <= l <= 6 for l in v)
if not valid_split_levels(header_split_levels):
header_split_levels = [1, 2, 3, 4, 5, 6] Type guard
def is_header_split_levels(v) -> bool:
return isinstance(v, list) and len(v) > 0 and all(isinstance(l, int) and not isinstance(l, bool) and 1 <= l <= 6 for l in v) Try / catch
try:
splitter = MarkdownHeaderSplitter(header_split_levels=levels)
except ValueError as e:
logging.warning("Bad header_split_levels (%s), using default", e)
splitter = MarkdownHeaderSplitter() Prevention
- Never pass a bare int; always wrap a single level in a list.
- Omit the parameter when you want all heading levels (the default).
- Validate config-driven lists for type and non-emptiness before construction.
- Remember bool is a subclass of int; sanitize user input with explicit int checks.
When it happens
Trigger: Calling MarkdownHeaderSplitter(header_split_levels=[]) or header_split_levels set to a non-list value such as a string, tuple, or int (e.g. header_split_levels=2 instead of [2]).
Common situations: Passing a single int instead of a list, building the levels list dynamically and ending up empty, or deserializing component config from JSON/YAML where the list was omitted or emptied.
Related errors
- min_effective_lines must be at least 1.
- max_effective_lines must be at least 1.
- expected_chars_per_line must be at least 1.
- oversized_factor must be at least 1.
- secondary_split_overlap must be non-negative.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/30e31c2d10c45bb9.
Report an issue: GitHub.