{"record":{"id":"5462ba4b9f61f020","repo":"tinyhumansai/openhuman","slug":"composio-direct-execute-tool-slug-must-not-be-emp","errorCode":null,"errorMessage":"composio direct_execute: tool slug must not be empty","messagePattern":"composio direct_execute: tool slug must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":940,"sourceCode":"/// the v3 `/tools/{slug}/execute` envelope into [`ComposioExecuteResponse`]\n/// so the caller doesn't branch on mode for the\n/// `ComposioActionExecuted` event-bus payload or the\n/// markdown-vs-JSON-body preference.\n///\n/// Direct mode runs without the backend's billing margin, so `cost_usd`\n/// is reported as `0.0`. The backend's `markdownFormatted` field is\n/// likewise specific to the backend-proxied path and remains `None` for\n/// direct callers, which fall back to the raw JSON envelope.\npub async fn direct_execute(\n    direct: &Arc<crate::openhuman::tools::ComposioTool>,\n    tool: &str,\n    arguments: Option<serde_json::Value>,\n    entity_id: &str,\n    connection_id: Option<&str>,\n) -> anyhow::Result<ComposioExecuteResponse> {\n    let tool = tool.trim();\n    if tool.is_empty() {\n        anyhow::bail!(\"composio direct_execute: tool slug must not be empty\");\n    }\n    let params = arguments.unwrap_or_else(|| serde_json::Value::Object(Default::default()));\n    let entity_id = entity_id.trim();\n    let entity_id_opt = (!entity_id.is_empty()).then_some(entity_id);\n    let conn_id = connection_id.map(str::trim).filter(|s| !s.is_empty());\n    tracing::debug!(\n        tool = %tool,\n        has_entity = entity_id_opt.is_some(),\n        connection_id = ?conn_id,\n        \"[composio-direct] execute: invoking v3 /tools/{{slug}}/execute\"\n    );\n    let raw = direct\n        .execute_action(tool, params, entity_id_opt, conn_id)\n        .await?;\n    // v3 surfaces `successful` + `data` + `error` at the top level. If\n    // none are present, treat the call as success so callers see the\n    // raw payload instead of an empty error envelope.\n    let successful = raw","sourceCodeStart":922,"sourceCodeEnd":958,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L922-L958","documentation":"direct_execute invokes Composio's v3 /tools/{slug}/execute endpoint in direct mode; it rejects a tool slug that is empty after trimming before building params or the URL. It is the direct-mode counterpart of execute_tool's guard, so blank slugs fail identically on both the proxied and direct paths.","triggerScenarios":"Calling direct_execute(&direct_tool, \"\", arguments, entity_id, connection_id) or with a whitespace-only slug — the same LLM/caller bugs as the proxied path: model tool call with a blank action name.","commonSituations":"Agent harness forwarding model output unvalidated into the direct path; slug assembled from parts where one was empty; direct-mode scripts looping over a list containing blank entries.","solutions":["Validate and trim the slug in the caller before invoking direct mode, and re-prompt or skip when blank","Filter blank entries when building tool lists for batch execution","Share one slug-validation helper across the proxied and direct call sites so both paths guard identically"],"exampleFix":"// before\nlet resp = direct_execute(&tool, action.as_str(), Some(args), entity, conn).await?;\n\n// after\nlet action = action.trim();\nif action.is_empty() {\n    anyhow::bail!(\"direct composio execute received an empty tool slug\");\n}\nlet resp = direct_execute(&tool, action, Some(args), entity, conn).await?;","handlingStrategy":"validation","validationCode":"let tool = tool.trim();\nif tool.is_empty() {\n    anyhow::bail!(\"tool slug is required for direct composio execution\");\n}\nlet resp = direct_execute(&direct_tool, tool, arguments, entity_id, connection_id).await?;","typeGuard":"fn is_non_empty_slug(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":"match direct_execute(&tool, slug, args, entity, conn).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 once in the agent harness so both proxied and direct paths receive clean input","Filter blank slugs when iterating tool lists for batch execution","Treat an empty action name from a model as a re-prompt condition, not an error to surface to users"],"tags":["composio","execute","direct-mode","validation","empty-string"],"backgroundTag":"empty-string-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}