{"record":{"id":"4d1c920332a372b0","repo":"tinyhumansai/openhuman","slug":"composio-v3-action-execution-failed-err","errorCode":null,"errorMessage":"Composio v3 action execution failed: {err}","messagePattern":"Composio v3 action execution failed: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/tools/direct.rs","lineNumber":462,"sourceCode":"            params,\n            entity_id,\n            connected_account_ref,\n        );\n        let url = format!(\"{}/tools/execute/{action_slug}\", self.base_v3);\n\n        self.ensure_request_url(&url)?;\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 action execution failed: {err}\");\n        }\n\n        let result: serde_json::Value = resp\n            .json()\n            .await\n            .context(\"Failed to decode Composio v3 execute response\")?;\n        Ok(result)\n    }\n\n    async fn execute_action_v2(\n        &self,\n        action_name: &str,\n        params: serde_json::Value,\n        entity_id: Option<&str>,\n    ) -> anyhow::Result<serde_json::Value> {\n        let url = format!(\"{}/actions/{action_name}/execute\", self.base_v2);\n\n        let mut body = json!({","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/tools/direct.rs#L444-L480","documentation":"Non-2xx from the v3 action-execution POST. The request body is built by build_execute_action_v3_request (action slug, params, entity_id, connected_account_ref) and the URL passes ensure_request_url; response_error folds status + body into {err}. This is the primary execution path — the v2 fallback only runs after this already failed.","triggerScenarios":"4xx param validation failures; expired or deleted connectedAccount referenced by connected_account_ref; unknown/wrong-case action slug; 401 invalid API key; 429/5xx. Example: executing with a disconnected account yields a 4xx explaining the missing connected account.","commonSituations":"Agent passes user-derived strings as params without matching the action schema; connected account revoked upstream; slug casing copied from v2 docs.","solutions":["Inspect {err} — Composio's body names the exact invalid field or missing account","Re-authenticate the app via the connect flow if the connected account is expired/absent","Match the action slug exactly (v3 UPPER_SNAKE) and validate params against the tool schema from list_tool_schemas_v3","429/5xx: retry with backoff only when safe"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match tool.execute_action_v3_path(slug, params, entity, account).await {\n    Ok(v) => Ok(v),\n    Err(e) => {\n        let msg = format!(\"{e:#}\");\n        if msg.contains(\"400\") || msg.contains(\"422\") {\n            return Err(anyhow::anyhow!(\"invalid params for {slug}: {msg}\")); // not retryable\n        }\n        Err(e)\n    }\n}","preventionTips":["Validate params against list_tool_schemas_v3 output before executing","Track connected-account freshness and re-auth before expiry instead of executing into a 4xx","Reserve retries for 429/5xx and only when the action is idempotent"],"tags":["composio","execute","http-error","v3","action"],"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"}