{"record":{"id":"0e0e9fd92e6fa0eb","repo":"tinyhumansai/openhuman","slug":"composio-v3-connected-accounts-failed-err","errorCode":null,"errorMessage":"Composio v3 connected_accounts failed: {err}","messagePattern":"Composio v3 connected_accounts failed: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/tools/direct.rs","lineNumber":645,"sourceCode":"        let url = format!(\"{}/connected_accounts\", self.base_v3);\n        self.ensure_request_url(&url)?;\n\n        let resp = self\n            .client()\n            .get(&url)\n            .header(\"x-api-key\", &self.api_key)\n            // Composio paginates; pull a generous page size so most\n            // users see their full list in one round trip. If a user has\n            // > 200 connected accounts (extremely rare for an individual\n            // tenant) the rest will be missing until we add explicit\n            // pagination — note for the follow-up.\n            .query(&[(\"limit\", \"200\")])\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = response_error(resp).await;\n            anyhow::bail!(\"Composio v3 connected_accounts failed: {err}\");\n        }\n\n        let mut body: ComposioConnectedAccountsResponse = resp\n            .json()\n            .await\n            .context(\"Failed to decode Composio v3 connected_accounts response\")?;\n        // Drop rows with a blank id — serde_default means id can be \"\"\n        // if the upstream response is malformed. An empty connectionId\n        // propagated downstream causes invalid v3 API calls.\n        body.items.retain(|item| !item.id.trim().is_empty());\n        tracing::debug!(\n            count = body.items.len(),\n            \"[composio-direct] list_connected_accounts: fetched connected accounts\"\n        );\n        Ok(body.items)\n    }\n\n    async fn resolve_auth_config_id(&self, app_name: &str) -> anyhow::Result<String> {","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/tools/direct.rs#L627-L663","documentation":"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.","triggerScenarios":"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.","commonSituations":"Revoked key while the poller keeps running; many accounts and tight poll interval hitting the limit page; transient Composio errors during deploys.","solutions":["Inspect {err}: 401 → re-enter a valid key in Connections > Composio; 429 → lengthen the poll interval; 5xx → wait and retry","Verify the key with curl -H 'x-api-key: <key>' https://backend.composio.dev/api/v3/connected_accounts","If you administrate the poller, honor the direct_auth backoff gate instead of hammering through 401s"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let key_id = tool.auth_key_fingerprint();\nif let Some(gate) = direct_auth::direct_auth_backoff_error(key_id) {\n    return Err(anyhow::anyhow!(gate));\n}\nmatch tool.list_connected_accounts().await {\n    Ok(items) => { direct_auth::record_direct_auth_success(key_id); Ok(items) }\n    Err(e) => {\n        let rendered = format!(\"{e:#}\");\n        if let direct_auth::DirectAuthFailureDecision::CircuitOpened { consecutive } =\n            direct_auth::record_direct_auth_failure(key_id, &rendered)\n        { return Err(anyhow::anyhow!(direct_auth::invalid_api_key_backoff_message(consecutive))); }\n        Err(e)\n    }\n}","preventionTips":["Route every direct connected_accounts call through the direct_auth recorder so 401s actually trip the breaker","Lengthen poll intervals to stay clear of 429s with 200-account pages","Degrade the connections UI to the last known list during transient 5xx instead of showing an error storm"],"tags":["composio","connected-accounts","http-error","v3","polling"],"backgroundTag":"upstream-api-error-status","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}