tinyhumansai/openhuman · warning · anyhow::Error

fetch_toolkit_actions: toolkit must not be empty

Error message

fetch_toolkit_actions: toolkit must not be empty

What it means

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.

Source

Thrown at src/openhuman/integrations/composio/connected_integrations.rs:1024

/// `fetch_connected_integrations_uncached`'s own namespacing rule so
/// siblings like `github` / `git` don't leak into each other's buckets.
///
/// `tags` narrows the result by Composio action tag (OR semantics). Only
/// honoured for the GitHub toolkit; passed through to `list_tools` so the
/// backend can skip the repo-list force-include and return a focused set.
///
/// Returns an empty vec when the backend has no actions for the
/// toolkit (valid steady state for a freshly-authorised integration
/// whose catalogue hasn't been published yet). Returns `Err` only for
/// transport / auth failures the caller should surface to the user.
pub async fn fetch_toolkit_actions(
    client: &ComposioClient,
    toolkit: &str,
    tags: Option<&[String]>,
) -> anyhow::Result<Vec<ConnectedIntegrationTool>> {
    let toolkit_slug = toolkit.trim();
    if toolkit_slug.is_empty() {
        anyhow::bail!("fetch_toolkit_actions: toolkit must not be empty");
    }
    let effective_tags = if should_forward_tags(Some(&[toolkit_slug.to_string()])) {
        tags
    } else {
        None
    };
    tracing::debug!(toolkit = %toolkit_slug, ?effective_tags, "[composio] fetch_toolkit_actions");
    let resp = client
        .list_tools(Some(&[toolkit_slug.to_string()]), effective_tags)
        .await
        .map_err(|e| anyhow::anyhow!("list_tools failed for toolkit `{toolkit_slug}`: {e}"))?;
    let action_prefix = format!("{}_", toolkit_slug.to_uppercase());
    // Apply curated whitelist + user scope so spawn-time tool
    // discovery agrees with the bulk path and the meta-tool layer.
    let pref = super::providers::load_user_scope_or_default(toolkit_slug).await;
    let actions: Vec<ConnectedIntegrationTool> = resp
        .tools
        .into_iter()

View on GitHub (pinned to 7491200858)

Solutions

  1. Trim and validate the toolkit slug at the call site before invoking fetch_toolkit_actions
  2. If the slug comes from a connected-integration record, skip rows with a blank toolkit field instead of passing them through
  3. When the slug originates from user input, reject empty values early with an actionable message

Example fix

// before
let actions = fetch_toolkit_actions(&client, toolkit, tags).await?;

// after
let slug = toolkit.trim();
if slug.is_empty() {
    anyhow::bail!("toolkit slug is required to list actions");
}
let actions = fetch_toolkit_actions(&client, slug, tags).await?;
Defensive patterns

Strategy: validation

Validate before calling

let slug = toolkit.trim();
if slug.is_empty() {
    anyhow::bail!("toolkit slug is required to list actions");
}
let actions = fetch_toolkit_actions(&client, slug, tags).await?;

Type guard

fn is_valid_toolkit_slug(raw: &str) -> bool {
    !raw.trim().is_empty()
}

Try / catch

match fetch_toolkit_actions(&client, toolkit, tags).await {
    Ok(actions) => { /* render */ }
    Err(e) if format!("{e:#}").contains("toolkit must not be empty") => {
        // caller bug / blank row — skip silently rather than crash the listing loop
    }
    Err(e) => return Err(e), // transport/auth failures surface to the user
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/1a968c90bd6cbb32. Report an issue: GitHub.