{"record":{"id":"78135cff56edd86b","repo":"zeroclaw-labs/zeroclaw","slug":"composio-v3-connect-failed-err","errorCode":null,"errorMessage":"Composio v3 connect failed: {err}","messagePattern":"Composio v3 connect failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/composio.rs","lineNumber":459,"sourceCode":"        };\n\n        let url = format!(\"{COMPOSIO_API_BASE_V3}/connected_accounts/link\");\n        let body = json!({\n            \"auth_config_id\": auth_config_id,\n            \"user_id\": entity_id,\n        });\n\n        let resp = self\n            .client()\n            .post(&url)\n            .header(\"x-api-key\", &self.api_key)\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let err = response_error(resp).await;\n            anyhow::bail!(\"Composio v3 connect failed: {err}\");\n        }\n\n        let result: serde_json::Value = resp\n            .json()\n            .await\n            .context(\"Failed to decode Composio v3 connect response\")?;\n        let redirect_url = extract_redirect_url(&result).ok_or_else(|| {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                \"composio: v3 response missing redirect URL\"\n            );\n            anyhow::Error::msg(\"No redirect URL in Composio v3 response\")\n        })?;\n        Ok(ComposioConnectionLink {\n            redirect_url,\n            connected_account_id: extract_connected_account_id(&result),","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/composio.rs#L441-L477","documentation":"get_connection_url_v3 mints an OAuth redirect URL by POSTing {auth_config_id, user_id} to {v3}/connected_accounts/link; this error means that call returned non-2xx. auth_config_id is either the explicit argument or one resolved from the app name via resolve_auth_config_id (which prefers enabled configs). Raised from action='connect'; the Tool wrapper reports it as 'Failed to get connection URL: Composio v3 connect failed: ...' in ToolResult.error.","triggerScenarios":"action='connect' with an auth_config_id that is deleted, belongs to another workspace, or is disabled (400/404); invalid x-api-key (401); rate limiting (429); Composio 5xx; a user_id the API rejects.","commonSituations":"Copying auth_config_id between dev and prod workspaces; attempting connect before the app's credential setup is finished in the Composio dashboard; API key rotated while the old config id is still cached in agent prompts.","solutions":["Read the HTTP code: 400/404 means the auth_config_id is wrong — retry with app='<toolkit slug>' and let the tool resolve a valid enabled config itself.","Verify the auth config exists, is enabled, and belongs to the same workspace as the API key in the Composio dashboard.","On 401, fix composio.api_key.","On 429/5xx, retry — link generation is idempotent."],"exampleFix":"// before: stale id copied from another workspace\nlet args = json!({\"action\": \"connect\", \"auth_config_id\": \"cfg-from-dev-workspace\"});\n\n// after: connect by app so the tool resolves the workspace's own config\nlet args = json!({\"action\": \"connect\", \"app\": \"github\", \"entity_id\": \"alice\"});","handlingStrategy":"retry","validationCode":"// Prefer app-name connects; confirm the app exists first\nlet actions = tool.list_actions(Some(app)).await?;\nif actions.is_empty() {\n    anyhow::bail!(\"unknown Composio app '{app}'\");\n}\nlet link = tool.get_connection_url(Some(app), None, entity_id).await?;","typeGuard":"fn valid_connect_target(app: Option<&str>, auth_config_id: Option<&str>) -> bool {\n    match (app, auth_config_id) {\n        (Some(a), _) if !a.trim().is_empty() => true,\n        (_, Some(id)) if !id.trim().is_empty() => true,\n        _ => false,\n    }\n}","tryCatchPattern":"match tool.execute(json!({\"action\": \"connect\", \"app\": app})).await {\n    Ok(r) if r.success => { /* open redirect_url */ }\n    Ok(r) => {\n        let e = r.error.unwrap_or_default();\n        if [\"HTTP 429\", \"HTTP 500\", \"HTTP 502\", \"HTTP 503\", \"HTTP 504\"].iter().any(|c| e.contains(c)) {\n            // retry after backoff; link generation is idempotent\n        } else {\n            // bad config id or key: fall back to app-name connect / fix the key\n        }\n    }\n    Err(e) => { /* transport error */ }\n}","preventionTips":["Connect by app name and let Composio pick the enabled config","Store auth_config_ids per environment; never share across workspaces","Keep exactly one enabled auth config per toolkit so resolution is deterministic"],"tags":["composio","rust","oauth","connect","http"],"backgroundTag":"oauth-connect-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}