tinyhumansai/openhuman · error · anyhow::Error

composio direct authorize: toolkit must not be empty

Error message

composio direct authorize: toolkit must not be empty

What it means

direct_authorize (the direct-mode, non-backend-proxied authorize) rejects a toolkit name that is empty after trimming before requesting a hosted connect URL. It mirrors the backend-proxied authorize guard; an empty entity_id is tolerated and defaults to "default", but the toolkit is mandatory.

Source

Thrown at src/openhuman/integrations/composio/client.rs:891

/// reshapes the response into the [`ComposioAuthorizeResponse`] the
/// backend-proxied path emits.
///
/// The v3 endpoint returns a redirect URL but does NOT (currently)
/// surface a stable `connection_id` in the same call — the connection
/// row is created lazily when the user completes OAuth on Composio's
/// hosted page. To preserve the response contract the frontend already
/// consumes, we emit an empty `connection_id` for now. The 5 s
/// `list_connections` poll (now live in direct mode too — see
/// [`direct_list_connections`]) is what ultimately surfaces the new
/// row to the UI.
pub(super) async fn direct_authorize(
    direct: &Arc<crate::openhuman::tools::ComposioTool>,
    toolkit: &str,
    entity_id: &str,
) -> anyhow::Result<ComposioAuthorizeResponse> {
    let toolkit = toolkit.trim();
    if toolkit.is_empty() {
        anyhow::bail!("composio direct authorize: toolkit must not be empty");
    }
    let entity_id = entity_id.trim();
    let entity_id = if entity_id.is_empty() {
        "default"
    } else {
        entity_id
    };
    tracing::debug!(
        toolkit = %toolkit,
        entity_id = %entity_id,
        "[composio-direct] authorize: requesting hosted connect URL"
    );
    let connect_url = direct
        .get_connection_url(Some(toolkit), None, entity_id)
        .await?;
    tracing::debug!(
        toolkit = %toolkit,
        url_len = connect_url.len(),

View on GitHub (pinned to 7491200858)

Solutions

  1. Enforce toolkit selection before the authorize action is reachable
  2. Trim and validate the toolkit id at the boundary and fail with UI-friendly context
  3. Cross-check the toolkit against the direct-mode catalog when one is available

Example fix

// before
let resp = direct_authorize(&tool, toolkit_param, entity).await?;

// after
let toolkit = toolkit_param.trim();
if toolkit.is_empty() {
    return Err(anyhow::anyhow!("toolkit is required to start a direct authorize"));
}
let resp = direct_authorize(&tool, toolkit, entity).await?;
Defensive patterns

Strategy: validation

Validate before calling

let toolkit = toolkit.trim();
if toolkit.is_empty() {
    anyhow::bail!("toolkit is required for direct composio authorize");
}
let resp = direct_authorize(&direct_tool, toolkit, entity_id).await?;

Type guard

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

Prevention

When it happens

Trigger: Calling direct_authorize(&direct_tool, "", entity_id) in direct mode — same producer bugs as the proxied path: unselected toolkit in the connection UI, or a missing config key.

Common situations: Direct-mode connection form submitted without a toolkit; toolkit id sourced from a renamed config field; scripted bulk-authorize iterating an incomplete toolkit list.

Related errors


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