nautechsystems/nautilus_trader · error
Failed to decode position replay state: {e}
Error message
Failed to decode position replay state: {e} What it means
load_position found a position snapshot whose replay_state JSON value failed serde_json deserialization into the expected replay state type. This error means the persisted snapshot is present but its embedded replay state does not match the expected structure.
Source
Thrown at crates/infrastructure/src/sql/queries.rs:942
.map(|rows| rows.into_iter().map(|row| row.0).collect())
.map_err(|e| anyhow::anyhow!("Failed to load position events: {e}"))
}
/// Loads and replays a complete `Position` for a `position_id` via the provided `pool`.
///
/// # Errors
///
/// Returns an error if loading events, loading instruments, or replaying fills fails.
pub async fn load_position(
pool: &PgPool,
position_id: &PositionId,
) -> anyhow::Result<Option<Position>> {
if let Some(snapshot) = Self::load_position_snapshot(pool, position_id).await?
&& let Some(replay_state) = snapshot.replay_state
{
return serde_json::from_value(replay_state)
.map(Some)
.map_err(|e| anyhow::anyhow!("Failed to decode position replay state: {e}"));
}
let fills = Self::load_position_events(pool, position_id).await?;
let Some((first_fill, remaining_fills)) = fills.split_first() else {
return Ok(None);
};
let Some(instrument) = Self::load_instrument(pool, &first_fill.instrument_id).await? else {
log::error!(
"Instrument not found for position {position_id}: {}",
first_fill.instrument_id
);
return Ok(None);
};
let mut position = Position::new(&instrument, first_fill.clone());
for fill in remaining_fills {
if position.trade_ids().contains(&fill.trade_id) {
anyhow::bail!(View on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the wrapped serde_json error to see which field/type failed.
- Compare the stored replay_state JSON against the current Rust struct and migrate old snapshots.
- If snapshots are optional for you, clear the snapshot row so load_position falls back to replaying raw fill events via load_position_events.
- Ensure writes and reads use the same nautilus version, or run the provided data migrations.
Example fix
// before
let position = DatabaseQuery::load_position(&pool, &position_id).await?;
// after
let position = match DatabaseQuery::load_position(&pool, &position_id).await {
Ok(p) => p,
Err(e) if e.to_string().contains("Failed to decode position replay state") => {
tracing::warn!("Snapshot unusable for {position_id}, rebuilding from events: {e:#}");
None // fall back to event replay / re-snapshot
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: fallback
Try / catch
match load_position(&pool, &pid).await {
Ok(p) => p,
Err(e) if e.to_string().contains("Failed to decode position replay state") => {
rebuild_from_events(&pool, &pid).await.ok()
}
Err(e) => return Err(e),
} Prevention
- Use the same nautilus version for snapshot writes and reads.
- Run schema/data migrations on version upgrades.
- Prefer deleting an invalid snapshot row over editing it — event replay rebuilds state.
When it happens
Trigger: Calling load_position(pool, position_id) where load_position_snapshot returns a snapshot with a replay_state that serde_json cannot deserialize — wrong shape, missing fields, or type changes between versions.
Common situations: Snapshot written by an older nautilus version whose Position replay state schema changed; manually edited snapshot rows; truncated/corrupted JSON stored in the snapshot column.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- from_json not implemented for {}
- serde_json deserialization error: {e}
- Failed to serialize exec algorithm params: {e}
- Failed to serialize order event info: {e}
- {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7e696da827a975c4.
Report an issue: GitHub.