tinyhumansai/openhuman · error · anyhow::Error

composio.execute_tool: tool slug must not be empty

Error message

composio.execute_tool: tool slug must not be empty

What it means

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.

Source

Thrown at src/openhuman/integrations/composio/auth_retry.rs:78

}

/// Test-visible inner form that takes an explicit backoff so unit tests
/// can drive the retry path without sleeping for real seconds.
pub(crate) async fn execute_with_auth_retry_inner(
    client: &ComposioClient,
    slug: &str,
    args: Option<serde_json::Value>,
    backoff: Duration,
    connection_id: Option<&str>,
) -> anyhow::Result<ComposioExecuteResponse> {
    let tool = slug.trim();
    if tool.is_empty() {
        tracing::debug!(
            target: "composio",
            raw_slug_len = slug.len(),
            "[composio][auth_retry] rejecting empty tool slug"
        );
        anyhow::bail!("composio.execute_tool: tool slug must not be empty");
    }
    let arguments = args.unwrap_or(serde_json::Value::Object(Default::default()));
    let has_args = arguments.as_object().is_some_and(|a| !a.is_empty());
    let mut body = serde_json::json!({ "tool": tool, "arguments": arguments });
    if let Some(cid) = connection_id.map(str::trim).filter(|s| !s.is_empty()) {
        body["connectionId"] = serde_json::json!(cid);
    }

    tracing::debug!(
        target: "composio",
        slug = %tool,
        has_args,
        connection_id = ?connection_id,
        "[composio][auth_retry] execute start"
    );
    client
        .execute_tool_with_post_oauth_retry(tool, &body, backoff)
        .await

View on GitHub (pinned to 7491200858)

Solutions

  1. Fix the caller that produced the slug — an empty slug is always a caller bug, never a Composio state problem
  2. Trim and validate the slug before invoking, and re-prompt the agent (or skip) when it is blank
  3. If slugs come from parsed tool definitions, assert non-empty at parse time so the bad data is caught at its source

Example fix

// before
let resp = execute_with_auth_retry(&slug, args, backoff, conn).await?;

// after
let slug = slug.trim();
if slug.is_empty() {
    anyhow::bail!("agent produced an empty composio action slug; re-prompt");
}
let resp = execute_with_auth_retry(slug, args, backoff, conn).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Validate before entering the auth-retry path
let slug = slug.trim();
if slug.is_empty() {
    tracing::warn!("[agent] composio tool call had an empty slug; skipping");
    return Ok(None); // or re-prompt the model for a valid action
}
let resp = execute_with_auth_retry(slug, args, backoff, connection_id).await?;

Type guard

fn is_non_empty_slug(s: &str) -> bool {
    !s.trim().is_empty()
}

Try / catch

match execute_with_auth_retry(slug, args, backoff, conn).await {
    Ok(resp) => Ok(resp),
    Err(err) if err.to_string().contains("tool slug must not be empty") => {
        re_prompt_agent_for_action().await // caller bug: recover by asking again
    }
    Err(err) => Err(err),
}

Prevention

When it happens

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

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

Related errors


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