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

unknown replication message byte `{}`

Error message

unknown replication message byte `{}`

What it means

The Postgres logical-replication message parser dispatches on a leading byte identifying the message/tuple-data type. When it encounters a byte it has no match arm for, it raises InvalidInput 'unknown replication message byte `{}`'. This indicates the stream contains a message type this parser doesn't implement (or the stream is misaligned/corrupt).

Source

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

        for _ in 0..number_of_columns {
            let byte = buf.read_u8()?;
            let tuple_data = match byte {
                TUPLE_DATA_NULL_BYTE => TupleData::Null,
                TUPLE_DATA_TOAST_BYTE => TupleData::UnchangedToast,
                TUPLE_DATA_TEXT_BYTE => {
                    let len = buf.read_i32::<BigEndian>()?;
                    let mut data = vec![0; len as usize];
                    buf.read_exact(&mut data)?;
                    TupleData::Text(data.into())
                }
                TUPLE_DATA_BINARY_BYTE => {
                    let len = buf.read_i32::<BigEndian>()?;
                    let mut data = vec![0; len as usize];
                    buf.read_exact(&mut data)?;
                    TupleData::Binary(data.into())
                }
                byte => {
                    return Err(ConversionError::Io(io::Error::new(
                        io::ErrorKind::InvalidInput,
                        format!("unknown replication message byte `{}`", byte),
                    )));
                }
            };

            tuples.push(tuple_data);
        }

        Ok(tuples)
    }
}

#[derive(Debug)]
pub enum TransactionBody {
    Insert(InsertBody),
    Update(UpdateBody),
    Delete(DeleteBody),

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check which byte value was reported and compare against the pgoutput protocol to identify the unhandled message type.
  2. Upgrade the windmill-trigger-postgres parser (or Windmill itself) to a version supporting that message type.
  3. Drop and recreate the replication slot/publication so the stream restarts aligned.
  4. Verify wal_level=logical and the pgoutput protocol options match what the parser expects.
Defensive patterns

Strategy: try-catch

Try / catch

match parse(msg_bytes) {
    Err(ConversionError::Io(e)) if e.to_string().contains("unknown replication message byte") => {
        tracing::warn!("unsupported pgoutput message: {e}; skipping and resyncing slot");
        // recreate slot or upgrade parser
    }
    Ok(msg) => handle(msg),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Postgres sends a logical replication message type (or TupleData sub-message byte) not covered by the parser's match — e.g. a newer Postgres version emitting unsupported message kinds, or the buffer being read at a wrong offset after a partial/failed parse.

Common situations: Upgrading the Postgres server to a version emitting new replication message types; parsing a pgoutput stream with a plugin/format mismatch; corrupted or misaligned replication slot stream after a previous parse error.

Related errors


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