linera-io/linera-protocol · error

Invalid stream ID: {s}

Error message

Invalid stream ID: {s}

What it means

StreamId::from_str splits the string on the LAST colon (rsplit_once): the left part must parse as a GenericApplicationId ('System' or 'User:<id>'), the right part as a StreamName (hex bytes). If there is no colon at all, this error is raised; a colon present but malformed parts surfaces the chained 'Invalid GenericApplicationId!'/'Invalid StreamName!' errors instead.

Source

Thrown at linera-base/src/identifiers.rs:707

        Display::fmt(&self.stream_name, f)
    }
}

impl std::str::FromStr for StreamId {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts = s.rsplit_once(":");
        if let Some((part0, part1)) = parts {
            let application_id =
                GenericApplicationId::from_str(part0).context("Invalid GenericApplicationId!")?;
            let stream_name = StreamName::from_str(part1).context("Invalid StreamName!")?;
            Ok(StreamId {
                application_id,
                stream_name,
            })
        } else {
            Err(anyhow!("Invalid stream ID: {s}"))
        }
    }
}

/// An event identifier.
#[derive(
    Debug,
    PartialEq,
    Eq,
    Hash,
    Clone,
    Serialize,
    Deserialize,
    WitLoad,
    WitStore,
    WitType,
    SimpleObject,
    Allocative,

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Use the full Display form '<application-id>:<stream-name-hex>', e.g. 'User:e40c...:a1b2'
  2. Stream names are hex-encoded bytes (StreamName::from_str uses hex::decode), so keep them valid hex
  3. Round-trip through StreamId::to_string() rather than manual formatting

Example fix

// before: bare stream name, no colon
let id: StreamId = stream_name_hex.parse()?; // Err: Invalid stream ID

// after: application id + ':' + hex stream name
let id: StreamId = format!("System:{stream_name_hex}").parse()?;
Defensive patterns

Strategy: type-guard

Validate before calling

fn looks_like_stream_id(s: &str) -> bool {
    let Some((app, name)) = s.rsplit_once(':') else { return false };
    (app == "System" || app.starts_with("User:"))
        && !name.is_empty()
        && name.bytes().all(|b| b.is_ascii_hexdigit())
}

Type guard

fn parse_stream_id(s: &str) -> Option<StreamId> {
    s.parse().ok()
}

Prevention

When it happens

Trigger: Calling parse::<StreamId>() on a string with no ':' at all, e.g. a bare stream name like 'a1b2c3' or an application ID alone. Valid shapes are 'System:a1b2' and 'User:e40c...:a1b2'.

Common situations: Config or query parameters that carry only the stream name; copying the StreamName component instead of the full StreamId; truncation when the hex stream name is dropped.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/3f39fa164ac7abb6. Report an issue: GitHub.