tinyhumansai/openhuman · error · anyhow::Error
Composio v3 API error: {err}
Error message
Composio v3 API error: {err} What it means
Non-2xx from the v3 GET /tools listing (list_actions_v3). The request pins limit=200 and toolkit_versions=latest (without that pin Composio serves the 00000000_00 snapshot and lists zero tools for post-launch toolkits), and adds toolkits/toolkit_slug filters when an app name is given. response_error(resp) folds the HTTP status and response body into {err}, which this bail re-raises.
Source
Thrown at src/openhuman/integrations/composio/tools/direct.rs:231
}
}
async fn list_actions_v3(&self, app_name: Option<&str>) -> anyhow::Result<Vec<ComposioAction>> {
let url = format!("{}/tools", self.base_v3);
let mut req = self.client().get(&url).header("x-api-key", &self.api_key);
// #3932: pin toolkit_versions=latest. Composio v3 otherwise defaults to
// the 00000000_00 snapshot, which lists zero tools for any toolkit
// published after it (Outlook and every other post-launch toolkit).
req = req.query(&[("limit", "200"), ("toolkit_versions", "latest")]);
if let Some(app) = app_name.map(str::trim).filter(|app| !app.is_empty()) {
req = req.query(&[("toolkits", app), ("toolkit_slug", app)]);
}
let resp = req.send().await?;
if !resp.status().is_success() {
let err = response_error(resp).await;
anyhow::bail!("Composio v3 API error: {err}");
}
let body: ComposioToolsResponse = resp
.json()
.await
.context("Failed to decode Composio v3 tools response")?;
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)View on GitHub (pinned to 7491200858)
Solutions
- Inspect {err} — it contains the HTTP status and Composio's response body
- 401: fix the API key (Connections > Composio)
- 429: back off and retry; the client does not throttle for you
- 400: correct the app/toolkit slug spelling used for toolkits/toolkit_slug
- 5xx: transient — retry with backoff and check Composio status
Defensive patterns
Strategy: try-catch
Try / catch
match tool.list_actions(Some(app)).await {
Ok(actions) => { /* ... */ }
Err(e) => {
let msg = format!("{e:#}");
match () {
_ if msg.contains("401") => ui.prompt_reenter_composio_key(),
_ if msg.contains("429") || msg.contains("50") => scheduler.retry_with_backoff(),
_ => tracing::warn!("[composio] tools listing failed: {msg}"),
}
}
} Prevention
- Read the status inside {err} before deciding to retry — 401 and 400 are not retryable
- Keep toolkit_versions=latest in any custom query or post-launch toolkits list zero tools
- Rate-limit listing calls; Composio answers 429 quickly when polled hard
When it happens
Trigger: 401 invalid API key; 400 for a malformed/unknown toolkit slug in the toolkits or toolkit_slug query param; 429 rate limit; 5xx from Composio. Surfaced directly when list_actions_v3 is the failing path, or embedded inside the combined v3+v2 error when the fallback also fails.
Common situations: Bad key or typo'd app name ('gh' vs 'github'); polling too aggressively and hitting rate limits during a Composio incident; a toolkit slug renamed upstream.
Related errors
- Composio v3 list_tool_schemas: {err}
- Composio v2 API error: {err}
- Composio v3 action execution failed: {err}
- Composio v3 connect failed: {err}
- Composio v3 connected_accounts failed: {err}
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/72c7231a60fab457.
Report an issue: GitHub.