zeroclaw-labs/zeroclaw · error

qos must be 0, 1, or 2, got {}

Error message

qos must be 0, 1, or 2, got {}

What it means

MQTT defines exactly three QoS levels: 0 (at-most-once), 1 (at-least-once), 2 (exactly-once). `MqttConfig::qos` is a `u8` with serde default 1, so the validator must reject any value above 2 before the channel connects, since the underlying MQTT client cannot map 3–255 to a protocol level.

Source

Thrown at crates/zeroclaw-config/src/schema.rs:16255

    /// are not exposed to the model when responding via this channel.
    #[tab(Behavior)]
    #[serde(default)]
    pub excluded_tools: Vec<String>,
}

impl MqttConfig {
    /// Validate the MQTT configuration.
    ///
    /// Checks:
    /// - QoS is 0, 1, or 2
    /// - broker_url uses valid scheme (`mqtt://` or `mqtts://`)
    /// - `use_tls` flag matches broker_url scheme
    /// - At least one topic is configured
    /// - client_id is non-empty
    pub fn validate(&self) -> anyhow::Result<()> {
        // QoS validation
        if self.qos > 2 {
            anyhow::bail!("qos must be 0, 1, or 2, got {}", self.qos);
        }

        // Broker URL validation
        let is_tls_scheme = self.broker_url.starts_with("mqtts://");
        let is_mqtt_scheme = self.broker_url.starts_with("mqtt://");

        if !is_tls_scheme && !is_mqtt_scheme {
            anyhow::bail!(
                "broker_url must start with 'mqtt://' or 'mqtts://', got: {}",
                self.broker_url
            );
        }

        // TLS flag validation
        if is_mqtt_scheme && self.use_tls {
            anyhow::bail!("use_tls is true but broker_url uses 'mqtt://' (not 'mqtts://')");
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set qos to 0, 1, or 2 — 1 (the default) is correct for most SOP listeners.
  2. If you need exactly-once-style semantics for critical alerts, use 2 knowingly (it adds handshake overhead).
  3. If the value came from code, clamp or validate the source before writing config.

Example fix

# before
[channels.mqtt.broker]
qos = 3

# after
[channels.mqtt.broker]
qos = 1
Defensive patterns

Strategy: validation

Validate before calling

fn valid_qos(qos: u8) -> bool { qos <= 2 }
// before building/loading config:
anyhow::ensure!(valid_qos(cfg.qos), "qos {} out of range", cfg.qos);

Type guard

fn valid_qos(qos: u8) -> bool { qos <= 2 }

Prevention

When it happens

Trigger: `qos = 3` (or any 3–255 value) in a `[channels.mqtt.<alias>]` block; computing qos programmatically and passing an out-of-range u8; deserializing config from JSON where qos was stored as a larger integer.

Common situations: Assuming higher QoS numbers mean 'more reliable' and inventing 3; porting configs from systems with unrelated numeric delivery guarantees; typos (e.g. 11 for 1).

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/bde1d74cdcf9b51d. Report an issue: GitHub.