headroomlabs-ai/headroom · error · ValueError

memory_ids ({len(memory_ids)}) and texts ({len(texts)}) must

Error message

memory_ids ({len(memory_ids)}) and texts ({len(texts)}) must have same length

What it means

FTS5TextIndex.index_batch validates that its parallel list arguments have identical lengths before touching SQLite; memory_ids and texts must pair one-to-one. This ValueError fails fast so the executemany DELETE/INSERT inside a single transaction never processes misaligned rows.

Source

Thrown at headroom/memory/adapters/fts5.py:162

    def index_batch(
        self,
        memory_ids: list[str],
        texts: list[str],
        metadata: list[dict] | None = None,
    ) -> None:
        """Index multiple memories in a single transaction.

        Args:
            memory_ids: List of unique identifiers.
            texts: List of text contents to index.
            metadata: Optional list of metadata dicts (one per memory).

        Raises:
            ValueError: If memory_ids and texts have different lengths.
        """
        if len(memory_ids) != len(texts):
            raise ValueError(
                f"memory_ids ({len(memory_ids)}) and texts ({len(texts)}) must have same length"
            )

        if metadata is not None and len(metadata) != len(memory_ids):
            raise ValueError(
                f"metadata ({len(metadata)}) must match memory_ids ({len(memory_ids)}) length"
            )

        metadata = metadata or [{} for _ in memory_ids]

        with self._get_conn() as conn:
            # Delete existing entries
            conn.executemany(
                "DELETE FROM memory_fts WHERE memory_id = ?",
                [(mid,) for mid in memory_ids],
            )

            # Prepare batch data

View on GitHub (pinned to 322425c43b)

Solutions

  1. Inspect the two counts in the message to find where the divergence starts.
  2. Build pairs and derive both lists from one structure: [(mid, text), ...] so they cannot drift.
  3. If chunking, repeat the memory_id per chunk instead of producing separate-length lists.
  4. Add an assert len(ids) == len(texts) in your producer code near the source of the mismatch.

Example fix

# before
index.index_batch(ids, chunked_texts)  # 10 ids, 23 chunks -> ValueError

# after
pairs = [(mid, chunk) for mid, chunks in zip(ids, chunked) for chunk in chunks]
index.index_batch([p[0] for p in pairs], [p[1] for p in pairs])
Defensive patterns

Strategy: validation

Validate before calling

def valid_batch(ids: list[str], texts: list[str], metadata: list | None) -> bool:
    """True when index_batch's parallel-array invariants hold."""
    if len(ids) != len(texts):
        return False
    return metadata is None or len(metadata) == len(ids)

Try / catch

try:
    index.index_batch(ids, texts)
except ValueError as e:
    if "must have same length" in str(e):
        raise SystemExit(f"producer bug: ragged batch ({e}); fix upstream list construction") from e
    raise

Prevention

When it happens

Trigger: Calling index_batch(memory_ids, texts) where the lists differ in length — e.g. ids generated per unique memory but texts per chunk, zipping that dropped elements, or a filtered list applied to only one of the two arguments.

Common situations: Chunking/dedup pipelines that shrink one list and not the other; batch code that appends ids for successful extractions but texts for all inputs; upstream scanner returning ragged batches; off-by-one slicing.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/79116f595a16e210. Report an issue: GitHub.