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
- 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
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
- 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
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
- composio.execute_tool: tool slug must not be empty
- composio.authorize: toolkit must not be empty
- composio.authorize: extra_params cannot override reserved ke
- composio.delete_connection: connectionId must not be empty
- composio.execute_tool: tool slug must not be empty
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/1a968c90bd6cbb32.
Report an issue: GitHub.