windmill-labs/windmill · error · ConversionError::Io (InvalidInput)

unknown replica identity byte `{}`

Error message

unknown replica identity byte `{}`

What it means

When parsing a Postgres logical replication Relation message, the replica identity setting is encoded as a single signed byte (d/n/f/i). An unrecognized value raises InvalidInput 'unknown replica identity byte `{}`'. Valid values correspond to DEFAULT, NOTHING, FULL, and INDEX replica identities.

Source

Thrown at backend/windmill-trigger-postgres/src/replication_message.rs:327

                buf.read_i64::<BigEndian>()?;
                LogicalReplicationMessage::Commit
            }
            RELATION_BYTE => {
                let transaction_id = match logical_replication_settings.streaming {
                    true => Some(buf.read_i32::<BigEndian>()?),
                    false => None,
                };

                let o_id = buf.read_u32::<BigEndian>()?;
                let namespace = buf.read_cstr()?;
                let name = buf.read_cstr()?;
                let replica_identity = match buf.read_i8()? {
                    REPLICA_IDENTITY_DEFAULT_BYTE => ReplicaIdentity::Default,
                    REPLICA_IDENTITY_NOTHING_BYTE => ReplicaIdentity::Nothing,
                    REPLICA_IDENTITY_FULL_BYTE => ReplicaIdentity::Full,
                    REPLICA_IDENTITY_INDEX_BYTE => ReplicaIdentity::Index,
                    byte => {
                        return Err(ConversionError::Io(io::Error::new(
                            io::ErrorKind::InvalidInput,
                            format!("unknown replica identity byte `{}`", byte),
                        )));
                    }
                };

                let num_of_column = buf.read_i16::<BigEndian>()?;

                let mut columns = Vec::with_capacity(num_of_column as usize);
                for _ in 0..num_of_column {
                    let flags = buf.read_i8()?;
                    let name = buf.read_cstr()?;
                    let o_id = buf.read_u32::<BigEndian>()?;
                    let type_modifier = buf.read_i32::<BigEndian>()?;
                    let type_o_id = Type::from_oid(o_id);
                    let column = Column::new(flags, name, type_o_id, type_modifier);

                    columns.push(column);

View on GitHub (pinned to e474e8803c)

Solutions

  1. Log/inspect the offending byte to confirm whether it's a misalignment artifact (often an ASCII letter from shifted data).
  2. Drop and recreate the replication slot to restart the stream aligned.
  3. Upgrade Windmill's replication parser if a new Postgres version introduced a new replica identity mode.
  4. Check for intermediaries (proxies, TLS termination) corrupting the replication connection.
Defensive patterns

Strategy: try-catch

Try / catch

match parse(msg_bytes) {
    Err(ConversionError::Io(e)) if e.to_string().contains("unknown replica identity byte") => {
        tracing::warn!("bad replica identity byte in relation message: {e}; resyncing slot");
    }
    Ok(msg) => handle(msg),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A Relation (table mapping) replication message carries a replica-identity byte outside {d, n, f, i} — due to protocol/version mismatch, stream misalignment from an earlier bad parse, or corruption.

Common situations: Streams parsed after a desynchronization; custom/newer Postgres encodings; binary corruption in transport; hand-crafted or proxied replication streams.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/f927448b7961f133. Report an issue: GitHub.