{"record":{"id":"4319c8931305ae79","repo":"zeroclaw-labs/zeroclaw","slug":"missing-app-or-auth-config-id-for-connect","errorCode":null,"errorMessage":"Missing 'app' or 'auth_config_id' for connect","messagePattern":"Missing 'app' or 'auth_config_id' for connect","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/composio.rs","lineNumber":809,"sourceCode":"            }\n\n            \"connect\" => {\n                if let Err(error) = self\n                    .security\n                    .enforce_tool_operation(ToolOperation::Act, \"composio.connect\")\n                {\n                    return Ok(ToolResult {\n                        success: false,\n                        output: ToolOutput::default(),\n                        error: Some(error),\n                    });\n                }\n\n                let app = args.get(\"app\").and_then(|v| v.as_str());\n                let auth_config_id = args.get(\"auth_config_id\").and_then(|v| v.as_str());\n\n                if app.is_none() && auth_config_id.is_none() {\n                    anyhow::bail!(\"Missing 'app' or 'auth_config_id' for connect\");\n                }\n\n                match self\n                    .get_connection_url(app, auth_config_id, entity_id)\n                    .await\n                {\n                    Ok(link) => {\n                        let target =\n                            app.unwrap_or(auth_config_id.unwrap_or(\"provided auth config\"));\n                        let mut output =\n                            format!(\"Open this URL to connect {target}:\\n{}\", link.redirect_url);\n                        if let Some(connected_account_id) = link.connected_account_id.as_deref() {\n                            if let Some(app_name) = app {\n                                self.cache_connected_account(\n                                    app_name,\n                                    entity_id,\n                                    connected_account_id,\n                                );","sourceCodeStart":791,"sourceCodeEnd":827,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/composio.rs#L791-L827","documentation":"Hard input-validation bail in Tool::execute's 'connect' arm: neither args['app'] nor args['auth_config_id'] is present as a JSON string, so there is no target to connect. Unlike most composio failures, which come back as Ok(ToolResult{success:false, error:...}), this one propagates as an Err from execute() — callers that only inspect ToolResult fields will not see it.","triggerScenarios":"action='connect' with no other keys; app/auth_config_id present but not strings (null, numbers, nested objects — as_str() returns None); misspelled keys like 'appName' or 'authConfigId' that the arg extraction never reads.","commonSituations":"LLM omits the target app or uses wrong key casing; upstream code injects null placeholders for optional fields; JSON args built dynamically without checking the tool's parameters_schema().","solutions":["Pass at least one of app (toolkit slug like 'slack') or auth_config_id as a string.","Fix key names to exactly 'app' and 'auth_config_id'.","Validate args against the tool's parameters_schema() before calling execute (see defense guard)."],"exampleFix":"// before\nlet args = json!({\"action\": \"connect\"});\n\n// after\nlet args = json!({\"action\": \"connect\", \"app\": \"slack\", \"entity_id\": \"alice\"});","handlingStrategy":"validation","validationCode":"// Check connect args before invoking the tool\nfn require_connect_target(args: &serde_json::Value) -> anyhow::Result<()> {\n    let target = [\"app\", \"auth_config_id\"].iter().find_map(|k| args.get(k));\n    match target.and_then(|v| v.as_str()) {\n        Some(s) if !s.trim().is_empty() => Ok(()),\n        _ => anyhow::bail!(\"connect requires a non-empty 'app' or 'auth_config_id' string\"),\n    }\n}","typeGuard":"fn has_connect_target(args: &serde_json::Value) -> bool {\n    [\"app\", \"auth_config_id\"].iter().any(|k| {\n        args.get(k)\n            .and_then(|v| v.as_str())\n            .map(|s| !s.trim().is_empty())\n            .unwrap_or(false)\n    })\n}","tryCatchPattern":"// Note: this bail surfaces as Err from execute(), not as ToolResult{success:false}\nlet result = match tool.execute(args).await {\n    Ok(r) => r,\n    Err(e) if e.to_string().contains(\"Missing 'app' or 'auth_config_id'\") => {\n        ToolResult { success: false, output: Default::default(), error: Some(\"re-prompt for target app\".into()) }\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Enforce the tool's parameters_schema() client-side before execute","In agent prompts, mark 'app' as required for action='connect'","Use exactly the documented key names ('app', 'auth_config_id')"],"tags":["composio","rust","validation","connect","agent-tools"],"backgroundTag":"missing-required-parameter","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}