nautechsystems/nautilus_trader · error

position snapshot blob_ref {blob_ref} has empty position id

Error message

position snapshot blob_ref {blob_ref} has empty position id

What it means

parse_position_snapshot_blob_ref rejects a position-snapshot reference whose path segment after the prefix contains an empty position id, i.e. the blob_ref is malformed and cannot identify a snapshot.

Source

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

                    .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)?;
    let expected_prefix = format!("{}-", position_id.as_str());

    let Some(snapshot_uuid) = snapshot.id.as_str().strip_prefix(&expected_prefix) else {
        anyhow::bail!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Populate the actual PositionId string in the blob_ref before calling
  2. Check that the source position_id is non-empty (and valid) when building the ref
  3. Reject empty ids upstream so malformed refs never reach the cache API

Example fix

// before
let blob_ref = format!("cache://position-snapshots/{}/{}", "", index);
// after
assert!(!position_id.as_str().is_empty());
let blob_ref = format!("cache://position-snapshots/{}/{}", position_id, index);
Defensive patterns

Strategy: validation

Validate before calling

let rest = blob_ref.strip_prefix("cache://position-snapshots/").expect("prefix");
let (pid, _) = rest.rsplit_once('/').expect("separator");
assert!(!pid.is_empty(), "blob_ref has empty position id");

Type guard

fn has_non_empty_position_id(blob_ref: &str) -> bool {
    blob_ref.strip_prefix("cache://position-snapshots/")
        .and_then(|rest| rest.rsplit_once('/'))
        .map(|(pid, _)| !pid.is_empty())
        .unwrap_or(false)
}

Try / catch

match cache.position_snapshot_frame(&blob_ref) {
    Err(e) if e.to_string().contains("empty position id") => log::error!("blob_ref built from empty PositionId; check upstream data"),
    other => other?,
}

Prevention

When it happens

Trigger: Passing a blob_ref like 'cache://position-snapshots//3' where the position id segment is empty.

Common situations: An empty/None position_id interpolated into a format string during ref construction; data corruption dropping the id when serializing the ref.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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