{"record":{"id":"7ab373baec853676","repo":"zeroclaw-labs/zeroclaw","slug":"state-must-be-on-or-off","errorCode":null,"errorMessage":"state must be 'on' or 'off'","messagePattern":"state must be 'on' or 'off'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-hardware/src/peripherals/smartroom.rs","lineNumber":84,"sourceCode":"\n    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {\n        let device = args\n            .get(\"device\")\n            .and_then(|v| v.as_str())\n            .ok_or_else(|| anyhow::Error::msg(\"missing device\"))?;\n\n        let state = args\n            .get(\"state\")\n            .and_then(|v| v.as_str())\n            .ok_or_else(|| anyhow::Error::msg(\"missing state\"))?;\n\n        let pin = output_pin(device)\n            .ok_or_else(|| anyhow::Error::msg(format!(\"unknown output device: {}\", device)))?;\n\n        let value = match state {\n            \"on\" => 1,\n            \"off\" => 0,\n            _ => anyhow::bail!(\"state must be 'on' or 'off'\"),\n        };\n\n        let result = self\n            .transport\n            .request(\"gpio_write\", json!({ \"pin\": pin, \"value\": value }))\n            .await?;\n\n        Ok(result)\n    }\n}\n\n/// Tool: read a smart-room input device (currently only motion_sensor).\npub struct ReadDeviceTool {\n    pub transport: Arc<SerialTransport>,\n}\n\n#[async_trait]\nimpl Tool for ReadDeviceTool {","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-hardware/src/peripherals/smartroom.rs#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet state = raw_input; // \"ON\", \"1\", \" on\"...\nroom.execute(device, state).await?;\n\n// after\nlet state = match raw_input.trim().to_lowercase().as_str() {\n    \"1\" | \"true\" => \"on\",\n    \"0\" | \"false\" => \"off\",\n    s => s,\n};\nif state != \"on\" && state != \"off\" {\n    anyhow::bail!(\"state must be 'on' or 'off'\");\n}\nroom.execute(device, state).await?;","handlingStrategy":"type-guard","validationCode":"let state = state.trim().to_lowercase();\nif !is_valid_state(&state) {\n    anyhow::bail!(\"state must be 'on' or 'off', got {state:?}\");\n}\nroom.execute(device, &state).await?;","typeGuard":"fn is_valid_state(state: &str) -> bool {\n    matches!(state.trim().to_lowercase().as_str(), \"on\" | \"off\")\n}","tryCatchPattern":"match room.execute(device, &state).await {\n    Err(e) if format!(\"{e}\").contains(\"state must be 'on' or 'off'\") => {\n        // pure input rejection: normalize and retry, never a device fault\n    }\n    rest => rest,\n}","preventionTips":["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"],"tags":["input-validation","smartroom","enum-values","gpio"],"backgroundTag":"invalid-enum-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}