{"record":{"id":"eb674f6cb479f752","repo":"zeroclaw-labs/zeroclaw","slug":"unable-to-determine-tool-slug-for-action-name","errorCode":null,"errorMessage":"Unable to determine tool slug for '{action_name}'. Run action='list' with the relevant app first to prime the cache.{}","messagePattern":"Unable to determine tool slug for '(.+?)'\\. Run action='list' with the relevant app first to prime the cache\\.(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/composio.rs","lineNumber":224,"sourceCode":"        let mut slug_candidates = self.build_v3_slug_candidates(action_name);\n        let mut prime_error = None;\n        if slug_candidates.is_empty()\n            && let Some(app) = app_hint.as_deref()\n        {\n            match self.list_actions(Some(app)).await {\n                Ok(_) => {\n                    slug_candidates = self.build_v3_slug_candidates(action_name);\n                }\n                Err(err) => {\n                    prime_error = Some(format!(\n                        \"Failed to refresh action list for app '{app}': {err}\"\n                    ));\n                }\n            }\n        }\n\n        if slug_candidates.is_empty() {\n            anyhow::bail!(\n                \"Unable to determine tool slug for '{action_name}'. Run action='list' with the relevant app first to prime the cache.{}\",\n                prime_error\n                    .as_deref()\n                    .map(|msg| format!(\" ({msg})\"))\n                    .unwrap_or_default()\n            );\n        }\n\n        let mut v3_errors = Vec::new();\n        for slug in slug_candidates {\n            self.cache_action_slug(action_name, &slug);\n            match self\n                .execute_action_v3(\n                    &slug,\n                    params.clone(),\n                    text,\n                    normalized_entity_id.as_deref(),\n                    resolved_account_ref.as_deref(),","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/composio.rs#L206-L242","documentation":"execute_action needs at least one v3 tool-slug candidate before calling Composio. Candidates come from the in-memory action_slug_cache (primed by action='list') plus spelling variants from build_tool_slug_candidates, which always includes the trimmed original name — so with the current code the candidate list can only be empty when the effective action name is blank (empty or whitespace-only). The optional ' (...)' suffix reports why an automatic action-list refresh failed, but listing cannot fix a blank name; the message's 'prime the cache' advice applies to slug accuracy, not to this blank-name case.","triggerScenarios":"action='execute' where action_name is \"\" or all whitespace; or tool_slug present as an empty string — execute() reads tool_slug FIRST (args.get(\"tool_slug\").or_else(|| args.get(\"action_name\"))), so an empty tool_slug string shadows a valid action_name and reaches execute_action as a blank name, leaving zero candidates.","commonSituations":"An LLM emits {\"action\":\"execute\",\"tool_slug\":\"\"} or copies an empty optional field; upstream code defaults a missing tool_slug to \"\" instead of omitting the key; untrimmed user input that is only spaces.","solutions":["Send a non-empty action_name or tool_slug — ideally the exact slug returned by action='list'.","If both keys are sent, remove the blank tool_slug: its presence overrides action_name in execute().","Validate and trim tool arguments at the call site before invoking the tool (see defense guard).","Run action='list' with the relevant app to confirm the exact slug spelling and prime the cache."],"exampleFix":"// before: empty tool_slug shadows a valid action_name\nlet args = json!({\"action\": \"execute\", \"tool_slug\": \"\", \"action_name\": \"github-list-repositories\", \"params\": {}});\n\n// after: one clean, non-empty slug\nlet args = json!({\"action\": \"execute\", \"tool_slug\": \"github-list-repositories\", \"params\": {}});\n\n// defensive: drop blank tool_slug before calling the tool\nlet obj = args.as_object_mut().unwrap();\nif obj.get(\"tool_slug\").and_then(|v| v.as_str()).map_or(false, |s| s.trim().is_empty()) {\n    obj.remove(\"tool_slug\");\n}","handlingStrategy":"validation","validationCode":"// Normalize execute args before calling the tool: drop a blank tool_slug,\n// then require a non-blank action name.\nfn normalize_execute_args(args: &mut serde_json::Value) -> anyhow::Result<()> {\n    let obj = args\n        .as_object_mut()\n        .ok_or_else(|| anyhow::anyhow!(\"tool args must be an object\"))?;\n    let blank = |v: &serde_json::Value| v.as_str().map_or(true, |s| s.trim().is_empty());\n    if obj.get(\"tool_slug\").is_some_and(blank) {\n        obj.remove(\"tool_slug\");\n    }\n    let name = obj\n        .get(\"tool_slug\")\n        .or_else(|| obj.get(\"action_name\"))\n        .and_then(|v| v.as_str());\n    if name.map_or(true, |s| s.trim().is_empty()) {\n        anyhow::bail!(\"execute requires a non-empty tool_slug or action_name\");\n    }\n    Ok(())\n}","typeGuard":"// Mirrors execute()'s precedence: tool_slug wins over action_name.\nfn has_non_blank_action(args: &serde_json::Value) -> bool {\n    let name = args\n        .get(\"tool_slug\")\n        .or_else(|| args.get(\"action_name\"))\n        .and_then(|v| v.as_str());\n    name.map(|s| !s.trim().is_empty()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Never emit both tool_slug and action_name when one can be blank","Validate LLM-produced tool calls against parameters_schema() before execute","Prefer exact slugs captured from a prior action='list' response"],"tags":["composio","rust","validation","slug","agent-tools"],"backgroundTag":"missing-required-parameter","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}