nautechsystems/nautilus_trader · error
Invalid instrument key '{key}'
Error message
Invalid instrument key '{key}' What it means
parse_instrument_key strips the configured prefix from a Redis instrument key and requires a non-empty remainder; otherwise it cannot recover the InstrumentId and throws 'Invalid instrument key'. This happens before InstrumentId parsing, so the key either lacks the prefix or is empty after stripping.
Source
Thrown at crates/infrastructure/src/redis/queries.rs:1021
}
async fn read_hset(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
let result: HashMap<String, String> = conn.hgetall(key).await?;
let json = serde_json::to_string(&result)?;
Ok(vec![Bytes::from(json.into_bytes())])
}
async fn read_list(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
let result: Vec<Bytes> = conn.lrange(key, 0, -1).await?;
Ok(result)
}
}
fn parse_instrument_key(key: &str, prefix: &str) -> anyhow::Result<InstrumentId> {
let value = key
.strip_prefix(prefix)
.filter(|value| !value.is_empty())
.ok_or_else(|| anyhow::anyhow!("Invalid instrument key '{key}'"))?;
InstrumentId::from_str(value)
.map_err(|e| anyhow::anyhow!("Failed to parse instrument ID from key '{key}': {e}"))
}
fn is_timestamp_field(key: &str) -> bool {
let expire_match = key == "expire_time_ns";
let ts_match = key.starts_with("ts_");
expire_match || ts_match
}
fn convert_timestamps(value: &mut Value) {
match value {
Value::Object(map) => {
for (key, v) in map {
if is_timestamp_field(key)
&& let Value::Number(n) = v
&& let Some(n) = n.as_u64()
{View on GitHub (pinned to 18893faf8b)
Solutions
- Set the Redis key_prefix config to exactly match the prefix used when the cache was written.
- Use a dedicated Redis database (or separate DB index) so scan patterns do not match foreign keys.
- Delete non-conforming keys or move them out of the scanned pattern.
- Log/skip bad keys in custom tooling before handing them to the loader.
Example fix
// before: stored under "myapp:instruments:...", reader uses default prefix
let config = RedisCacheConfig { key_prefix: "nautilus".into(), .. };
// after
let config = RedisCacheConfig { key_prefix: "myapp".into(), .. }; Defensive patterns
Strategy: validation
Validate before calling
// Rust: check key prefix before loading
fn has_prefix(key: &str, prefix: &str) -> bool {
key.strip_prefix(prefix).map(|r| !r.is_empty()).unwrap_or(false)
} Try / catch
match load_instruments(&mut con, trader_key, encoding).await {
Ok(instruments) => instruments,
Err(e) if e.to_string().contains("Invalid instrument key") => {
tracing::warn!("non-conforming keys found: {e}; check key_prefix config");
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Keep RedisCacheConfig.key_prefix identical between snapshotting and loading sessions.
- Use a dedicated Redis DB index to avoid scanning unrelated application keys.
- Filter scanned keys by the configured prefix before feeding them to loaders.
When it happens
Trigger: load_instruments or load_instrument_closes (via load_all) iterating keys where one does not start with the expected prefix (e.g. prefix config mismatch, foreign keys matched by the scan pattern, or an empty key).
Common situations: The Redis key_prefix config differs from the one used when writing; a shared Redis database contains unrelated keys matching the scan pattern; keys written by another tool without the prefix; empty string keys.
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
- Failed to parse instrument ID from key '{key}': {e}
- Invalid `external_order_claims` type: {e}
- Invalid `external_order_claims` instrument ID {claim}: {e}
- heartbeat_interval_secs must be greater than 0
- Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2bf12878ef2c6d56.
Report an issue: GitHub.