headroomlabs-ai/headroom · error · ParseError::ImplausiblePreludeLengths

bedrock_eventstream_parse_failed

bedrock_eventstream_parse_failed

Error message

implausible prelude lengths: total_length={total_length}, headers_length={headers_length}

What it means

MemoryHandler initialization rejected the configured backend string (memory_handler.py:443). Only 'local' and 'qdrant-neo4j' are recognized; anything else — 'qdrant', 'sqlite', 'chroma', 'none', empty-but-set, or a typo — raises ValueError during initialize(), which surfaces at startup or first memory use.

Source

Thrown at crates/headroom-proxy/src/bedrock/eventstream.rs:149

        self.headers.get(":event-type").and_then(|v| v.as_str())
    }

    /// Read the `:message-type` header as a string. Bedrock uses
    /// `event` (data frames) vs `exception` (synchronous errors).
    pub fn message_type(&self) -> Option<&str> {
        self.headers.get(":message-type").and_then(|v| v.as_str())
    }
}

/// Errors surfaced by the parser. Per project rules these are
/// structured (not `String`) so the handler can dispatch on them.
#[derive(Debug, Error)]
pub enum ParseError {
    /// `total_length` smaller than the minimum a well-formed message
    /// requires (12-byte prelude, headers block, 4-byte trailing CRC).
    /// Wire-format violation; AWS would never emit this. Surfaced
    /// loudly so the handler can 5xx and log it.
    #[error(
        "implausible prelude lengths: total_length={total_length}, headers_length={headers_length}"
    )]
    ImplausiblePreludeLengths {
        total_length: u32,
        headers_length: u32,
    },
    /// `total_length` exceeds the configured `max_message_bytes` cap.
    /// The cap defaults to 32 MiB; configurable via
    /// [`EventStreamParser::with_max_message_bytes`].
    #[error("message too large: total_length={total_length} cap={cap}")]
    MessageTooLarge { total_length: u32, cap: usize },
    /// CRC32 of the first 8 bytes of the prelude did not match the
    /// 9th-12th bytes. Indicates corruption (in-flight bit-flip,
    /// truncated chunk, or — if persistent — wire-format version skew).
    #[error("prelude CRC mismatch: expected={expected:#010x} got={got:#010x}")]
    PreludeCrcMismatch { expected: u32, got: u32 },
    /// CRC32 of the entire message body did not match the trailing
    /// 4-byte CRC. Indicates the message bytes were corrupted in

View on GitHub (pinned to 322425c43b)

Solutions

  1. Set the backend to 'local' (embedded) or 'qdrant-neo4j' (external vector+graph stack)
  2. Check for typos/case in your config file, CLI flag, or environment variable before the memory handler initializes
  3. If you meant to disable memory, use the memory disable option rather than an invalid backend string

Example fix

# before (config)
memory_backend = "qdrant"

# after
memory_backend = "qdrant-neo4j"
Defensive patterns

Strategy: validation

Validate before calling

VALID_BACKENDS = {"local", "qdrant-neo4j"}
if config.get("backend") not in VALID_BACKENDS:
    raise SystemExit(f"memory backend must be one of {sorted(VALID_BACKENDS)}, got {config.get('backend')!r}")

Type guard

from typing import Literal, TypeGuard
MemoryBackend = Literal["local", "qdrant-neo4j"]
def is_valid_backend(v: str) -> TypeGuard[MemoryBackend]:
    return v in ("local", "qdrant-neo4j")

Prevention

When it happens

Trigger: Setting memory_backend/memory-backend config or CLI flag to a nonexistent backend name; renaming between versions (e.g. older 'qdrant' vs current 'qdrant-neo4j'); env-derived values with whitespace or wrong casing.

Common situations: Upgrading headroom and reusing an old config file; copy-pasting backend names from docs of a different version; disabling intent expressed as 'none' or 'off' which is not a valid backend.

Related errors


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