nautechsystems/nautilus_trader · error

malformed position snapshot blob_ref {blob_ref}

Error message

malformed position snapshot blob_ref {blob_ref}

What it means

After stripping the 'cache://position-snapshots/' prefix, the remainder must contain a '/' separating the position id from the frame index (rsplit_once('/')). This error means the remainder has no separator, so the blob_ref lacks a frame index component.

Source

Thrown at crates/common/src/cache/position.rs:450

        self.position_snapshots
            .keys()
            .filter(|position_id| {
                self.positions
                    .get(position_id)
                    .is_some_and(|position| position.borrow().instrument_id == *instrument_id)
            })
            .copied()
            .collect()
    }
}

fn parse_position_snapshot_blob_ref(blob_ref: &str) -> anyhow::Result<(PositionId, usize)> {
    let Some(rest) = blob_ref.strip_prefix("cache://position-snapshots/") else {
        anyhow::bail!("unsupported cache snapshot blob_ref {blob_ref}");
    };

    let Some((position_id, snapshot_index)) = rest.rsplit_once('/') else {
        anyhow::bail!("malformed position snapshot blob_ref {blob_ref}");
    };

    if position_id.is_empty() {
        anyhow::bail!("position snapshot blob_ref {blob_ref} has empty position id");
    }

    let snapshot_index = snapshot_index.parse::<usize>().map_err(|e| {
        anyhow::anyhow!("position snapshot blob_ref {blob_ref} has invalid frame index: {e}")
    })?;

    Ok((PositionId::new(position_id), snapshot_index))
}

fn decode_position_snapshot_blob(
    position_id: &PositionId,
    blob: &[u8],
) -> anyhow::Result<Position> {
    let snapshot = serde_json::from_slice::<Position>(blob)?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Append the frame index: 'cache://position-snapshots/<position_id>/<frame_index>'
  2. Regenerate the blob_ref via the cache's snapshot API instead of manual string construction
  3. Validate the ref shape with a quick check before calling (contains a '/' after the prefix)

Example fix

// before
let blob_ref = format!("cache://position-snapshots/{position_id}");
// after
let blob_ref = format!("cache://position-snapshots/{position_id}/{snapshot_index}");
Defensive patterns

Strategy: validation

Validate before calling

fn blob_ref_has_index(blob_ref: &str) -> bool {
    blob_ref.strip_prefix("cache://position-snapshots/")
        .map(|rest| rest.contains('/'))
        .unwrap_or(false)
}

Type guard

fn parse_ref_parts(blob_ref: &str) -> Option<(&str, &str)> {
    blob_ref.strip_prefix("cache://position-snapshots/")?.rsplit_once('/')
}

Try / catch

if let Err(e) = cache.restore_snapshot_blob(blob_ref, blob) {
    if e.to_string().contains("malformed position snapshot blob_ref") {
        // rebuild blob_ref with '/<index>' suffix and retry once
    }
}

Prevention

When it happens

Trigger: Calling position_snapshot_frame or restore_snapshot_blob with a blob_ref like 'cache://position-snapshots/P-123' (no '/<index>' suffix).

Common situations: Building the ref with only the position id; a writer/consumer version mismatch where older refs had no index; string slicing that dropped the trailing '/<index>'.

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/a5c7cf547e771d65. Report an issue: GitHub.