{"record":{"id":"7ce9c26a1edc002b","repo":"zeroclaw-labs/zeroclaw","slug":"composio-v3-connected-accounts-lookup-failed-err","errorCode":null,"errorMessage":"Composio v3 connected accounts lookup failed: {err}","messagePattern":"Composio v3 connected accounts lookup failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/composio.rs","lineNumber":129,"sourceCode":"            (\"statuses\", \"ACTIVE\"),\n            (\"statuses\", \"INITIATED\"),\n        ]);\n\n        if let Some(app) = app_name\n            .map(normalize_app_slug)\n            .filter(|app| !app.is_empty())\n        {\n            req = req.query(&[(\"toolkit_slugs\", app.as_str())]);\n        }\n\n        if let Some(entity) = entity_id {\n            req = req.query(&[(\"user_ids\", entity)]);\n        }\n\n        let resp = req.send().await?;\n        if !resp.status().is_success() {\n            let err = response_error(resp).await;\n            anyhow::bail!(\"Composio v3 connected accounts lookup failed: {err}\");\n        }\n\n        let body: ComposioConnectedAccountsResponse = resp\n            .json()\n            .await\n            .context(\"Failed to decode Composio v3 connected accounts response\")?;\n        Ok(body.items)\n    }\n\n    fn cache_connected_account(&self, app_name: &str, entity_id: &str, connected_account_id: &str) {\n        let key = connected_account_cache_key(app_name, entity_id);\n        self.recent_connected_accounts\n            .write()\n            .insert(key, connected_account_id.to_string());\n    }\n\n    fn get_cached_connected_account(&self, app_name: &str, entity_id: &str) -> Option<String> {\n        let key = connected_account_cache_key(app_name, entity_id);","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/composio.rs#L111-L147","documentation":"Raised by ComposioTool::list_connected_accounts when the GET to https://backend.composio.dev/api/v3/connected_accounts (filters: limit=50, statuses INITIALIZING/ACTIVE/INITIATED, optional toolkit_slugs and user_ids) returns a non-2xx status. The {err} part is built by response_error(): the HTTP status code plus the API's own error.message field when the body is JSON, with connected_account_id/entity_id/user_id values redacted and the text truncated to 240 chars. It surfaces directly from action='list_accounts'/'connected_accounts', and indirectly from action='execute' when connected_account_id is omitted and resolve_connected_account_ref must auto-resolve the account on cache miss (the anyhow error propagates unchanged through execute_action).","triggerScenarios":"Running action='list_accounts' (with or without app) while the x-api-key is invalid or revoked (401/403); a toolkit_slugs filter value Composio rejects (400); rate limiting during high-frequency agent loops (429); Composio backend errors (5xx). Also fires inside action='execute' with no connected_account_id: execute_action -> resolve_connected_account_ref -> this endpoint, so a bad API key or outage makes every execute fail with this message.","commonSituations":"Rotated or mistyped composio.api_key in zeroclaw config; free-tier rate limits hit by an agent that executes many Composio actions; an app slug with wrong spelling so the server-side filter fails (normalize_app_slug fixes case and underscores, not typos); an entity_id that does not exist in the Composio workspace behind the key; a Composio v3 incident.","solutions":["Inspect the HTTP code embedded in the message: 401/403 means the Composio API key is wrong or revoked — fix composio.api_key and verify with a cheap action='list' call.","On HTTP 429, back off and retry: the tool makes a single attempt per call with no built-in retry, so pace or wrap the call site.","Use the exact Composio toolkit slug for app (for example 'gmail'); a genuinely wrong app name still fails server-side.","Confirm entity_id exists in the workspace behind the API key; a mismatched user_ids filter fails or returns nothing useful.","On HTTP 5xx, check Composio status and retry later."],"exampleFix":"// before: execute with no connected_account_id dies inside account auto-resolution\nlet args = json!({\"action\": \"execute\", \"tool_slug\": \"github-list-repositories\"});\nlet res = tool.execute(args).await; // Err: 'connected accounts lookup failed' when key is bad\n\n// after: pre-flight the key with a cheap call, then pass connected_account_id explicitly\nlet probe = tool.execute(json!({\"action\": \"list\", \"app\": \"github\"})).await?;\nif !probe.success {\n    anyhow::bail!(\"composio credentials invalid: {:?}\", probe.error);\n}\nlet accounts = tool.execute(json!({\"action\": \"list_accounts\", \"app\": \"github\"})).await?;\n// take the account id from `accounts.output` and send it as connected_account_id","handlingStrategy":"retry","validationCode":"// Fail fast on missing credentials before wiring the tool into the agent\nlet api_key = config.composio_api_key.trim();\nif api_key.is_empty() {\n    anyhow::bail!(\"composio.api_key is not configured\");\n}\nlet tool = ComposioTool::new(api_key, config.entity_id.as_deref(), security);\n// Cheap auth probe: surfaces 401 immediately instead of during execute\nlet probe = tool.execute(json!({\"action\": \"list\", \"app\": \"gmail\"})).await?;\nif !probe.success {\n    anyhow::bail!(\"composio auth probe failed: {:?}\", probe.error);\n}","typeGuard":"fn is_retryable_composio_error(msg: &str) -> bool {\n    [\"HTTP 429\", \"HTTP 500\", \"HTTP 502\", \"HTTP 503\", \"HTTP 504\"]\n        .iter()\n        .any(|code| msg.contains(code))\n}","tryCatchPattern":"match tool.execute(args).await {\n    Ok(result) if result.success => { /* use output */ }\n    Ok(result) => {\n        let err = result.error.unwrap_or_default();\n        if is_retryable_composio_error(&err) {\n            // backoff (e.g. 2s then 10s) and retry once; the lookup is an idempotent GET\n        } else {\n            // config problem (401/403/400): surface to the operator, do not retry\n        }\n    }\n    Err(e) => { /* transport/timeout: also retryable */ }\n}","preventionTips":["Pre-flight the API key with action='list' at startup","Pass connected_account_id explicitly on execute to skip the auto-resolution call that hits this endpoint","Cache list_accounts results per (app, entity) instead of re-querying in loops","Keep toolkit slugs in sync with the Composio catalog"],"tags":["composio","rust","http","api-key","rate-limit","oauth"],"backgroundTag":"api-request-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}