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
- Log/inspect the offending byte to confirm whether it's a misalignment artifact (often an ASCII letter from shifted data).
- Drop and recreate the replication slot to restart the stream aligned.
- Upgrade Windmill's replication parser if a new Postgres version introduced a new replica identity mode.
- 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
- Recreate the slot on stream desync rather than tolerating repeated errors
- Upgrade the parser when upgrading Postgres server versions
- Verify table REPLICA IDENTITY settings are standard (DEFAULT/FULL/NOTHING/INDEX)
- Monitor for repeated byte errors as a sign of transport corruption
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
- unknown replication message byte `{}`
- unexpected tuple byte `{}`
- unexpected EOF
- unknown tuple byte `{}`
- unknown tuple tag `{}`
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/f927448b7961f133.
Report an issue: GitHub.