{"record":{"id":"4b585d81c6655ed6","repo":"tinyhumansai/openhuman","slug":"composio-execute-tool-tool-slug-must-not-be-empty","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/auth_retry.rs","lineNumber":78,"sourceCode":"}\n\n/// Test-visible inner form that takes an explicit backoff so unit tests\n/// can drive the retry path without sleeping for real seconds.\npub(crate) async fn execute_with_auth_retry_inner(\n    client: &ComposioClient,\n    slug: &str,\n    args: Option<serde_json::Value>,\n    backoff: Duration,\n    connection_id: Option<&str>,\n) -> anyhow::Result<ComposioExecuteResponse> {\n    let tool = slug.trim();\n    if tool.is_empty() {\n        tracing::debug!(\n            target: \"composio\",\n            raw_slug_len = slug.len(),\n            \"[composio][auth_retry] rejecting empty tool slug\"\n        );\n        anyhow::bail!(\"composio.execute_tool: tool slug must not be empty\");\n    }\n    let arguments = args.unwrap_or(serde_json::Value::Object(Default::default()));\n    let has_args = arguments.as_object().is_some_and(|a| !a.is_empty());\n    let mut body = serde_json::json!({ \"tool\": tool, \"arguments\": arguments });\n    if let Some(cid) = connection_id.map(str::trim).filter(|s| !s.is_empty()) {\n        body[\"connectionId\"] = serde_json::json!(cid);\n    }\n\n    tracing::debug!(\n        target: \"composio\",\n        slug = %tool,\n        has_args,\n        connection_id = ?connection_id,\n        \"[composio][auth_retry] execute start\"\n    );\n    client\n        .execute_tool_with_post_oauth_retry(tool, &body, backoff)\n        .await","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/auth_retry.rs#L60-L96","documentation":"execute_with_auth_retry rejects a tool slug that is empty after trimming, before building the request body. This is a client-side precondition guard on the auth-retry entry point, deliberately mirroring the guards in execute_tool and execute_tool_once so a blank slug never reaches Composio. It logs a debug record with the raw slug length before bailing.","triggerScenarios":"Calling composio::auth_retry::execute_with_auth_retry(\"\", args, backoff, connection_id) or with a slug of only whitespace. Typically an agent/LLM tool call whose action/slug field is missing or blank.","commonSituations":"LLM emits a tool call with an empty name but populated arguments; template/format! interpolation that produced an empty slug; caller defaults the slug to an empty string instead of erroring earlier.","solutions":["Fix the caller that produced the slug — an empty slug is always a caller bug, never a Composio state problem","Trim and validate the slug before invoking, and re-prompt the agent (or skip) when it is blank","If slugs come from parsed tool definitions, assert non-empty at parse time so the bad data is caught at its source"],"exampleFix":"// before\nlet resp = execute_with_auth_retry(&slug, args, backoff, conn).await?;\n\n// after\nlet slug = slug.trim();\nif slug.is_empty() {\n    anyhow::bail!(\"agent produced an empty composio action slug; re-prompt\");\n}\nlet resp = execute_with_auth_retry(slug, args, backoff, conn).await?;","handlingStrategy":"validation","validationCode":"// Validate before entering the auth-retry path\nlet slug = slug.trim();\nif slug.is_empty() {\n    tracing::warn!(\"[agent] composio tool call had an empty slug; skipping\");\n    return Ok(None); // or re-prompt the model for a valid action\n}\nlet resp = execute_with_auth_retry(slug, args, backoff, connection_id).await?;","typeGuard":"fn is_non_empty_slug(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":"match execute_with_auth_retry(slug, args, backoff, 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 // caller bug: recover by asking again\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Validate action names where model tool calls are deserialized, not deep in the HTTP client","Reject blank slugs in agent tool schemas (minLength on the action field) so producers fail early","Add a shared trim+check helper at every composio entry point instead of relying on the server-side bounce"],"tags":["composio","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"}