zeroclaw-labs/zeroclaw · warning
state must be 'on' or 'off'
Error message
state must be 'on' or 'off'
What it means
The SmartRoom peripheral's execute maps a device state command to gpio_write; only the exact lowercase strings "on" and "off" are accepted, mapping to values 1 and 0. Any other state string — "ON", "1", "true", "toggle", or values with surrounding whitespace — bails before any transport request is sent.
Source
Thrown at crates/zeroclaw-hardware/src/peripherals/smartroom.rs:84
async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
let device = args
.get("device")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::Error::msg("missing device"))?;
let state = args
.get("state")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::Error::msg("missing state"))?;
let pin = output_pin(device)
.ok_or_else(|| anyhow::Error::msg(format!("unknown output device: {}", device)))?;
let value = match state {
"on" => 1,
"off" => 0,
_ => anyhow::bail!("state must be 'on' or 'off'"),
};
let result = self
.transport
.request("gpio_write", json!({ "pin": pin, "value": value }))
.await?;
Ok(result)
}
}
/// Tool: read a smart-room input device (currently only motion_sensor).
pub struct ReadDeviceTool {
pub transport: Arc<SerialTransport>,
}
#[async_trait]
impl Tool for ReadDeviceTool {View on GitHub (pinned to 88bb9c8533)
Solutions
- Pass exactly "on" or "off" — lowercase, no whitespace
- Normalize before calling: trim and lowercase the state string, mapping synonyms like 1/true to "on"
- Validate upstream against an enum and reject unknown values with your own error before reaching the peripheral
Example fix
// before
let state = raw_input; // "ON", "1", " on"...
room.execute(device, state).await?;
// after
let state = match raw_input.trim().to_lowercase().as_str() {
"1" | "true" => "on",
"0" | "false" => "off",
s => s,
};
if state != "on" && state != "off" {
anyhow::bail!("state must be 'on' or 'off'");
}
room.execute(device, state).await?; Defensive patterns
Strategy: type-guard
Validate before calling
let state = state.trim().to_lowercase();
if !is_valid_state(&state) {
anyhow::bail!("state must be 'on' or 'off', got {state:?}");
}
room.execute(device, &state).await?; Type guard
fn is_valid_state(state: &str) -> bool {
matches!(state.trim().to_lowercase().as_str(), "on" | "off")
} Try / catch
match room.execute(device, &state).await {
Err(e) if format!("{e}").contains("state must be 'on' or 'off'") => {
// pure input rejection: normalize and retry, never a device fault
}
rest => rest,
} Prevention
- Normalize (trim + lowercase) user/agent input before calling the tool
- Expose only an on/off enum in your own tool schema so invalid values never reach the peripheral
- Map common synonyms (1/true, 0/false) in your adapter layer
When it happens
Trigger: Calling the smartroom execute with state = "ON" (case differs), "1" or "true" (synonyms), or " on" (whitespace) — the match arms compare the raw string with no normalization.
Common situations: Agent/LLM tool callers passing user-typed values verbatim; configs copied from Home Assistant-style integrations that accept on/off/true/false; capitalized or localized input.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- AcpChannel.request_choice requires at least one choice
- AcpChannel.request_multi_choice requires at least one choice
- Uno Q host must contain at most one '@'
- Invalid Uno Q SSH user: use only ASCII letters, digits, '.',
- Invalid Uno Q host: use a DNS hostname or IPv4 address witho
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7ab373baec853676.
Report an issue: GitHub.