{"record":{"id":"19470b0d544289e6","repo":"tinyhumansai/openhuman","slug":"composio-execute-tool-tool-slug-must-not-be-empty-19470b","errorCode":null,"errorMessage":"composio.execute_tool: tool slug must not be empty","messagePattern":"composio\\.execute_tool: tool slug must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":192,"sourceCode":"        } else {\n            format!(\"/agent-integrations/composio/tools?{}\", params.join(\"&\"))\n        };\n        tracing::debug!(path = %path, \"[composio] list_tools\");\n        self.inner.get::<ComposioToolsResponse>(&path).await\n    }\n\n    // ── Execute ─────────────────────────────────────────────────────\n\n    /// `POST /agent-integrations/composio/execute` — run a Composio\n    /// action and return the provider result + cost.\n    pub async fn execute_tool(\n        &self,\n        tool: &str,\n        arguments: Option<serde_json::Value>,\n    ) -> Result<ComposioExecuteResponse> {\n        let tool = tool.trim();\n        if tool.is_empty() {\n            anyhow::bail!(\"composio.execute_tool: tool slug must not be empty\");\n        }\n        // Egress spine (privacy epic S2, #4436): a Composio tool call ships the\n        // (already-normalized) arguments to the third-party provider — disclose\n        // the transfer before the round-trip. S4 will add an approval arm here.\n        let egress = crate::openhuman::security::egress::EgressDescriptor::composio(tool);\n        // Local-only enforcement (privacy epic S7, #4441): refuse the external\n        // tool call under LocalOnly BEFORE disclosing or sending it.\n        crate::openhuman::security::egress::enforce_egress(&egress)?;\n        crate::openhuman::security::egress::emit_external_transfer(egress);\n        // PR #1827 routes all execute-side argument normalization\n        // (including the bare-date → RFC 3339 fix #1802 brought to\n        // `normalize_calendar_query_args` on `main`) through the\n        // centralized `prepare_execute_arguments` helper. The helper\n        // covers the same calendar query case and is the shared entry\n        // point for `composio_execute`, per-action tools, and direct-\n        // mode dispatch.\n        let arguments = super::execute_prepare::prepare_execute_arguments(tool, arguments)\n            .map_err(anyhow::Error::msg)?;","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L174-L210","documentation":"ComposioClient::execute_tool rejects a tool slug that is empty after trimming, before the egress descriptor is built and the POST /agent-integrations/composio/execute request is sent. The guard runs before privacy disclosure (emit_external_transfer) so a blank slug never counts as an external transfer.","triggerScenarios":"Calling execute_tool(\"\", arguments) or with a whitespace-only slug — typically an agent/LLM tool invocation whose action name is missing while arguments are present.","commonSituations":"LLM emits a tool call with a blank function name; slug built by string concatenation where one part was empty; tool name read from a renamed/missing config or tool-definition key.","solutions":["Fix the producer of the slug — an empty slug is a caller bug, not a Composio failure","Validate and trim the slug where the tool call is parsed, and re-prompt or skip on blank","Cross-check slug values against the tool list from list_tools so typos and empties are both caught early"],"exampleFix":"// before\nlet resp = client.execute_tool(action.as_str(), Some(args)).await?;\n\n// after\nlet action = action.trim();\nif action.is_empty() {\n    anyhow::bail!(\"tool call arrived with an empty action slug\");\n}\nlet resp = client.execute_tool(action, Some(args)).await?;","handlingStrategy":"validation","validationCode":"let tool = tool.trim();\nif tool.is_empty() {\n    anyhow::bail!(\"tool slug is required for composio execution\");\n}\nlet resp = client.execute_tool(tool, arguments).await?;","typeGuard":"fn is_non_empty_slug(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":"match client.execute_tool(tool, arguments).await {\n    Ok(resp) => Ok(resp),\n    Err(err) if err.to_string().contains(\"tool slug must not be empty\") => {\n        re_prompt_agent_for_action().await\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Validate slugs where LLM tool calls are parsed — blank names are model output bugs","Cross-check requested slugs against list_tools output to catch blanks and typos in one pass","Never default a missing slug to an empty string; make it a required field with a schema error"],"tags":["composio","execute","validation","empty-string","argument-validation"],"backgroundTag":"empty-string-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}