{"record":{"id":"31f52e2356d6309d","repo":"nautechsystems/nautilus_trader","slug":"position-snapshot-id-does-not-match-blob-ref-po","errorCode":null,"errorMessage":"position snapshot id {} does not match blob_ref position {position_id}","messagePattern":"position snapshot id (.+?) does not match blob_ref position (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/common/src/cache/position.rs","lineNumber":472,"sourceCode":"        anyhow::bail!(\"position snapshot blob_ref {blob_ref} has empty position id\");\n    }\n\n    let snapshot_index = snapshot_index.parse::<usize>().map_err(|e| {\n        anyhow::anyhow!(\"position snapshot blob_ref {blob_ref} has invalid frame index: {e}\")\n    })?;\n\n    Ok((PositionId::new(position_id), snapshot_index))\n}\n\nfn decode_position_snapshot_blob(\n    position_id: &PositionId,\n    blob: &[u8],\n) -> anyhow::Result<Position> {\n    let snapshot = serde_json::from_slice::<Position>(blob)?;\n    let expected_prefix = format!(\"{}-\", position_id.as_str());\n\n    let Some(snapshot_uuid) = snapshot.id.as_str().strip_prefix(&expected_prefix) else {\n        anyhow::bail!(\n            \"position snapshot id {} does not match blob_ref position {position_id}\",\n            snapshot.id\n        );\n    };\n\n    if UUID4::from_str(snapshot_uuid).is_err() {\n        anyhow::bail!(\n            \"position snapshot id {} does not match blob_ref position {position_id}\",\n            snapshot.id\n        );\n    }\n\n    Ok(snapshot)\n}\n","sourceCodeStart":454,"sourceCodeEnd":487,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/common/src/cache/position.rs#L454-L487","documentation":"decode_position_snapshot_blob deserializes a JSON Position snapshot and verifies its 'id' has the form '<position_id>-<uuid4>'. This error means the snapshot's id does not start with the position id from the blob_ref, so the blob belongs to a different position than the ref claims.","triggerScenarios":"restore_snapshot_blob called with a (blob_ref, blob) pair that was mismatched — e.g. the blob stored under one position's ref actually contains another position's snapshot, or the position_id parsed from the ref was altered.","commonSituations":"Ref and blob stored/transported out of sync in external storage; hand-editing blob_refs; a cache backend that re-keyed blobs; version changes to the '<id>-<uuid>' snapshot id format.","solutions":["Verify the blob_ref and blob are a matched pair from the same write (same position)","Re-export the snapshot so the ref and blob are regenerated together","Check that external storage didn't reorder or re-key blobs relative to their refs"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let snapshot: Position = serde_json::from_slice(&blob)?;\nif !snapshot.id.as_str().starts_with(&format!(\"{position_id}-\")) {\n    return Err(anyhow!(\"blob does not belong to position {position_id}\"));\n}","typeGuard":"fn blob_matches_position(snapshot_id: &str, position_id: &PositionId) -> bool {\n    snapshot_id.starts_with(&format!(\"{}-\", position_id.as_str()))\n}","tryCatchPattern":"match cache.restore_snapshot_blob(&blob_ref, &blob) {\n    Err(e) if e.to_string().contains(\"does not match blob_ref position\") => {\n        // ref/blob pair is inconsistent: re-fetch or re-export the snapshot\n    }\n    other => other?,\n}","preventionTips":["Store ref and blob atomically as one record so they can't desynchronize","Never hand-edit or rewrite blob_refs when moving snapshots between storage backends"],"tags":["cache","snapshot","integrity-check","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}