tinyhumansai/openhuman · error · anyhow::Error

Composio v3 connected_accounts failed: {err}

Error message

Composio v3 connected_accounts failed: {err}

What it means

Non-2xx from GET v3 /connected_accounts (limit=200) — the core call of the direct-mode connection poller behind direct_list_connections. response_error folds status + body into {err}. A 401 'Invalid API key' here is exactly what feeds the consecutive-failure breaker; after enough repetitions the poller stops calling and surfaces the backoff message instead.

Source

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

        let url = format!("{}/connected_accounts", self.base_v3);
        self.ensure_request_url(&url)?;

        let resp = self
            .client()
            .get(&url)
            .header("x-api-key", &self.api_key)
            // Composio paginates; pull a generous page size so most
            // users see their full list in one round trip. If a user has
            // > 200 connected accounts (extremely rare for an individual
            // tenant) the rest will be missing until we add explicit
            // pagination — note for the follow-up.
            .query(&[("limit", "200")])
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = response_error(resp).await;
            anyhow::bail!("Composio v3 connected_accounts failed: {err}");
        }

        let mut body: ComposioConnectedAccountsResponse = resp
            .json()
            .await
            .context("Failed to decode Composio v3 connected_accounts response")?;
        // Drop rows with a blank id — serde_default means id can be ""
        // if the upstream response is malformed. An empty connectionId
        // propagated downstream causes invalid v3 API calls.
        body.items.retain(|item| !item.id.trim().is_empty());
        tracing::debug!(
            count = body.items.len(),
            "[composio-direct] list_connected_accounts: fetched connected accounts"
        );
        Ok(body.items)
    }

    async fn resolve_auth_config_id(&self, app_name: &str) -> anyhow::Result<String> {

View on GitHub (pinned to 7491200858)

Solutions

  1. Inspect {err}: 401 → re-enter a valid key in Connections > Composio; 429 → lengthen the poll interval; 5xx → wait and retry
  2. Verify the key with curl -H 'x-api-key: <key>' https://backend.composio.dev/api/v3/connected_accounts
  3. If you administrate the poller, honor the direct_auth backoff gate instead of hammering through 401s
Defensive patterns

Strategy: try-catch

Try / catch

let key_id = tool.auth_key_fingerprint();
if let Some(gate) = direct_auth::direct_auth_backoff_error(key_id) {
    return Err(anyhow::anyhow!(gate));
}
match tool.list_connected_accounts().await {
    Ok(items) => { direct_auth::record_direct_auth_success(key_id); Ok(items) }
    Err(e) => {
        let rendered = format!("{e:#}");
        if let direct_auth::DirectAuthFailureDecision::CircuitOpened { consecutive } =
            direct_auth::record_direct_auth_failure(key_id, &rendered)
        { return Err(anyhow::anyhow!(direct_auth::invalid_api_key_backoff_message(consecutive))); }
        Err(e)
    }
}

Prevention

When it happens

Trigger: 401 invalid API key (drives errors 280/281); 429 from polling too aggressively; 5xx during a Composio incident; malformed upstream body is a separate decode error — this one is purely the HTTP status branch.

Common situations: Revoked key while the poller keeps running; many accounts and tight poll interval hitting the limit page; transient Composio errors during deploys.

Related errors


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