tinyhumansai/openhuman · error · anyhow::Error

Composio v2 API error: {err}

Error message

Composio v2 API error: {err}

What it means

Non-2xx from the legacy v2 GET /actions endpoint (list_actions_v2), optionally filtered with ?appNames=<app>. response_error folds status + body into {err}. This endpoint is only reached as the compatibility fallback after v3 /tools already failed, so seeing it alone means a caller used the v2 path directly.

Source

Thrown at src/openhuman/integrations/composio/tools/direct.rs:256

        Ok(map_v3_tools_to_actions(body.items))
    }

    async fn list_actions_v2(&self, app_name: Option<&str>) -> anyhow::Result<Vec<ComposioAction>> {
        let mut url = format!("{}/actions", self.base_v2);
        if let Some(app) = app_name {
            url = format!("{url}?appNames={app}");
        }

        let resp = self
            .client()
            .get(&url)
            .header("x-api-key", &self.api_key)
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = response_error(resp).await;
            anyhow::bail!("Composio v2 API error: {err}");
        }

        let body: ComposioActionsResponse = resp
            .json()
            .await
            .context("Failed to decode Composio v2 actions response")?;
        Ok(body.items)
    }

    /// Build the query-parameter pairs for the Composio v3 `GET /tools`
    /// listing used by [`Self::list_tool_schemas_v3`].
    ///
    /// `toolkits` is sent as a single comma-joined `toolkits=` param (the
    /// legacy plural the v3 backend tolerates; cf. `list_actions_v3` which
    /// sends both the plural and `toolkit_slug` singular forms). `tags` is
    /// encoded as **repeated** `tags=` params (`tags=a&tags=b`) — the shape
    /// Composio v3 `/tools` documents for tag filtering ("can be specified
    /// multiple times"), NOT the comma-joined form the backend proxy uses.

View on GitHub (pinned to 7491200858)

Solutions

  1. Treat this as confirmation the failure is not v3-specific — check auth and network first (401/timeouts appearing in both messages)
  2. If v2 4xx differs from v3 4xx, the v2 appNames value may be wrong for the legacy API — prefer fixing the v3 failure and letting the fallback stay unused
  3. 429/5xx: retry with backoff
  4. For toolkits published after the v2 freeze, expect v2 to have no data — rely on the v3 path
Defensive patterns

Strategy: try-catch

Try / catch

match tool.list_actions(Some(app)).await {
    Ok(actions) => { /* ... */ }
    Err(e) => {
        let msg = format!("{e:#}");
        if msg.contains("v2 fallback") {
            // both versions failed — classify from the embedded pair before any retry
            classify_and_report(msg);
        }
    }
}

Prevention

When it happens

Trigger: Same failure classes as v3 but on v2: 401 invalid key, 400 unknown appNames value, 429, 5xx. Typically the second half of the combined 'failed on v3 ... and v2 fallback ...' message from list_actions.

Common situations: v2-only behavior differences: appNames expects the Composio v2 app key (e.g. 'GITHUB' vs 'github' depending on toolkit), or the toolkit only exists in v3 so v2 answers 404/400.

Related errors


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