nautechsystems/nautilus_trader · error

Invalid hash index payload for '{index_key}': expected field

Error message

Invalid hash index payload for '{index_key}': expected field-value pairs

What it means

insert_index in the Redis cache adapter validates that the payload for INDEX_ORDER_CLIENT (an HSET index) contains an even number of byte slices so they can be paired as hash field-value entries. When the value slice length is not a multiple of 2, it cannot form field-value pairs, so the insert is rejected with this error before any Redis command is issued. It prevents silently writing a truncated or mispaired hash.

Source

Thrown at crates/infrastructure/src/redis/cache.rs:982

        INDEX_ORDER_IDS
        | INDEX_ORDERS
        | INDEX_ORDERS_OPEN
        | INDEX_ORDERS_CLOSED
        | INDEX_ORDERS_EMULATED
        | INDEX_ORDERS_INFLIGHT
        | INDEX_POSITIONS
        | INDEX_POSITIONS_OPEN
        | INDEX_POSITIONS_CLOSED => {
            insert_set(pipe, key, value[0].as_ref());
            Ok(())
        }
        INDEX_ORDER_POSITION => {
            insert_hset(pipe, key, value[0].as_ref(), value[1].as_ref());
            Ok(())
        }
        INDEX_ORDER_CLIENT => {
            if !value.len().is_multiple_of(2) {
                anyhow::bail!(
                    "Invalid hash index payload for '{index_key}': expected field-value pairs"
                );
            }

            let entries = value
                .as_chunks::<2>()
                .0
                .iter()
                .map(|entry| (entry[0].as_ref(), entry[1].as_ref()))
                .collect::<Vec<(&[u8], &[u8])>>();
            pipe.hset_multiple(key, &entries);
            Ok(())
        }
        _ => anyhow::bail!("Index unknown '{index_key}' on insert"),
    }
}

fn insert_string(pipe: &mut Pipeline, key: &str, value: &[u8]) {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the caller's `value` construction for the order-client index and ensure every field is immediately followed by its value (even element count).
  2. Log/print value.len() and each element at the call site to find the unpaired element.
  3. If a field is optional, pass both field and value (e.g. empty value) rather than omitting one half of the pair.
  4. Add a unit test asserting the payload length is a multiple of 2 before calling insert.

Example fix

// before
let value = vec![client_order_id_bytes]; // odd: field without value
insert_index(pipe, key, value, INDEX_ORDER_CLIENT)?;
// after
let value = vec![client_order_id_bytes, order_id_bytes]; // field-value pair
insert_index(pipe, key, value, INDEX_ORDER_CLIENT)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
if value.len() % 2 != 0 {
    return Err(anyhow::anyhow!(
        "order-client index payload must contain field-value pairs; got {} elements",
        value.len()
    ));
}

Try / catch

// Python bindings
try:
    cache.insert(index_key, key, payload)
except RuntimeError as e:
    if "expected field-value pairs" in str(e):
        log.error("Mispaired index payload: %s", payload)
    else:
        raise

Prevention

When it happens

Trigger: Calling cache.insert with an index key of INDEX_ORDER_CLIENT where the `value: &[Bytes]` argument has an odd number of elements (e.g. only the client_order_id field without its value, or an extra stray element).

Common situations: Serialization/persistence code that appends index fields one at a time but drops the last value; a caller updated to add a new field pair but only partially writing it; hand-built payloads in custom adapters or test harnesses misaligning field/value ordering.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/29422c89c7360e30. Report an issue: GitHub.