{"record":{"id":"1a968c90bd6cbb32","repo":"tinyhumansai/openhuman","slug":"fetch-toolkit-actions-toolkit-must-not-be-empty","errorCode":null,"errorMessage":"fetch_toolkit_actions: toolkit must not be empty","messagePattern":"fetch_toolkit_actions: toolkit must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"src/openhuman/integrations/composio/connected_integrations.rs","lineNumber":1024,"sourceCode":"/// `fetch_connected_integrations_uncached`'s own namespacing rule so\n/// siblings like `github` / `git` don't leak into each other's buckets.\n///\n/// `tags` narrows the result by Composio action tag (OR semantics). Only\n/// honoured for the GitHub toolkit; passed through to `list_tools` so the\n/// backend can skip the repo-list force-include and return a focused set.\n///\n/// Returns an empty vec when the backend has no actions for the\n/// toolkit (valid steady state for a freshly-authorised integration\n/// whose catalogue hasn't been published yet). Returns `Err` only for\n/// transport / auth failures the caller should surface to the user.\npub async fn fetch_toolkit_actions(\n    client: &ComposioClient,\n    toolkit: &str,\n    tags: Option<&[String]>,\n) -> anyhow::Result<Vec<ConnectedIntegrationTool>> {\n    let toolkit_slug = toolkit.trim();\n    if toolkit_slug.is_empty() {\n        anyhow::bail!(\"fetch_toolkit_actions: toolkit must not be empty\");\n    }\n    let effective_tags = if should_forward_tags(Some(&[toolkit_slug.to_string()])) {\n        tags\n    } else {\n        None\n    };\n    tracing::debug!(toolkit = %toolkit_slug, ?effective_tags, \"[composio] fetch_toolkit_actions\");\n    let resp = client\n        .list_tools(Some(&[toolkit_slug.to_string()]), effective_tags)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"list_tools failed for toolkit `{toolkit_slug}`: {e}\"))?;\n    let action_prefix = format!(\"{}_\", toolkit_slug.to_uppercase());\n    // Apply curated whitelist + user scope so spawn-time tool\n    // discovery agrees with the bulk path and the meta-tool layer.\n    let pref = super::providers::load_user_scope_or_default(toolkit_slug).await;\n    let actions: Vec<ConnectedIntegrationTool> = resp\n        .tools\n        .into_iter()","sourceCodeStart":1006,"sourceCodeEnd":1042,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/connected_integrations.rs#L1006-L1042","documentation":"fetch_toolkit_actions refuses an empty toolkit slug before any HTTP call: toolkit.trim() must be non-empty, because the slug becomes the v3 /tools?toolkits=<slug> query and the <SLUG>_ action-name prefix filter. This is a caller-contract check, not a Composio backend error — a normal 'no actions yet' answer is an empty Vec, and only transport/auth failures are Err.","triggerScenarios":"Calling fetch_toolkit_actions(&client, \"\", ...) or with a whitespace-only slug — typically a slug derived from a connected-integration row whose toolkit field is blank, or unvalidated user/frontend input passed straight through.","commonSituations":"A freshly connected integration whose ComposioConnection row has an empty toolkit name; a mapping table keyed by app name with a missing entry; frontend sending an untrimmed empty string for the toolkit picker.","solutions":["Trim and validate the toolkit slug at the call site before invoking fetch_toolkit_actions","If the slug comes from a connected-integration record, skip rows with a blank toolkit field instead of passing them through","When the slug originates from user input, reject empty values early with an actionable message"],"exampleFix":"// before\nlet actions = fetch_toolkit_actions(&client, toolkit, tags).await?;\n\n// after\nlet slug = toolkit.trim();\nif slug.is_empty() {\n    anyhow::bail!(\"toolkit slug is required to list actions\");\n}\nlet actions = fetch_toolkit_actions(&client, slug, tags).await?;","handlingStrategy":"validation","validationCode":"let slug = toolkit.trim();\nif slug.is_empty() {\n    anyhow::bail!(\"toolkit slug is required to list actions\");\n}\nlet actions = fetch_toolkit_actions(&client, slug, tags).await?;","typeGuard":"fn is_valid_toolkit_slug(raw: &str) -> bool {\n    !raw.trim().is_empty()\n}","tryCatchPattern":"match fetch_toolkit_actions(&client, toolkit, tags).await {\n    Ok(actions) => { /* render */ }\n    Err(e) if format!(\"{e:#}\").contains(\"toolkit must not be empty\") => {\n        // caller bug / blank row — skip silently rather than crash the listing loop\n    }\n    Err(e) => return Err(e), // transport/auth failures surface to the user\n}","preventionTips":["Trim and check slugs at the boundary where user or upstream data enters","Filter connected-integration rows with blank toolkit fields before mapping them to fetch calls","Distinguish this precondition error (fix the caller) from transport errors (surface to user) in handling"],"tags":["composio","validation","empty-argument","toolkit","argument-precondition"],"backgroundTag":"empty-required-parameter","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}