{"record":{"id":"a19b78ba4f1f7382","repo":"tinyhumansai/openhuman","slug":"composio-direct-authorize-toolkit-must-not-be-emp","errorCode":null,"errorMessage":"composio direct authorize: toolkit must not be empty","messagePattern":"composio direct authorize: toolkit must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":891,"sourceCode":"/// reshapes the response into the [`ComposioAuthorizeResponse`] the\n/// backend-proxied path emits.\n///\n/// The v3 endpoint returns a redirect URL but does NOT (currently)\n/// surface a stable `connection_id` in the same call — the connection\n/// row is created lazily when the user completes OAuth on Composio's\n/// hosted page. To preserve the response contract the frontend already\n/// consumes, we emit an empty `connection_id` for now. The 5 s\n/// `list_connections` poll (now live in direct mode too — see\n/// [`direct_list_connections`]) is what ultimately surfaces the new\n/// row to the UI.\npub(super) async fn direct_authorize(\n    direct: &Arc<crate::openhuman::tools::ComposioTool>,\n    toolkit: &str,\n    entity_id: &str,\n) -> anyhow::Result<ComposioAuthorizeResponse> {\n    let toolkit = toolkit.trim();\n    if toolkit.is_empty() {\n        anyhow::bail!(\"composio direct authorize: toolkit must not be empty\");\n    }\n    let entity_id = entity_id.trim();\n    let entity_id = if entity_id.is_empty() {\n        \"default\"\n    } else {\n        entity_id\n    };\n    tracing::debug!(\n        toolkit = %toolkit,\n        entity_id = %entity_id,\n        \"[composio-direct] authorize: requesting hosted connect URL\"\n    );\n    let connect_url = direct\n        .get_connection_url(Some(toolkit), None, entity_id)\n        .await?;\n    tracing::debug!(\n        toolkit = %toolkit,\n        url_len = connect_url.len(),","sourceCodeStart":873,"sourceCodeEnd":909,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L873-L909","documentation":"direct_authorize (the direct-mode, non-backend-proxied authorize) rejects a toolkit name that is empty after trimming before requesting a hosted connect URL. It mirrors the backend-proxied authorize guard; an empty entity_id is tolerated and defaults to \"default\", but the toolkit is mandatory.","triggerScenarios":"Calling direct_authorize(&direct_tool, \"\", entity_id) in direct mode — same producer bugs as the proxied path: unselected toolkit in the connection UI, or a missing config key.","commonSituations":"Direct-mode connection form submitted without a toolkit; toolkit id sourced from a renamed config field; scripted bulk-authorize iterating an incomplete toolkit list.","solutions":["Enforce toolkit selection before the authorize action is reachable","Trim and validate the toolkit id at the boundary and fail with UI-friendly context","Cross-check the toolkit against the direct-mode catalog when one is available"],"exampleFix":"// before\nlet resp = direct_authorize(&tool, toolkit_param, entity).await?;\n\n// after\nlet toolkit = toolkit_param.trim();\nif toolkit.is_empty() {\n    return Err(anyhow::anyhow!(\"toolkit is required to start a direct authorize\"));\n}\nlet resp = direct_authorize(&tool, toolkit, entity).await?;","handlingStrategy":"validation","validationCode":"let toolkit = toolkit.trim();\nif toolkit.is_empty() {\n    anyhow::bail!(\"toolkit is required for direct composio authorize\");\n}\nlet resp = direct_authorize(&direct_tool, toolkit, entity_id).await?;","typeGuard":"fn is_non_empty_toolkit(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Reuse the same toolkit validation for proxied and direct authorize paths","Filter blank entries when batch-authorizing toolkits from a list","Make toolkit a required request field in the RPC schema so the boundary rejects it first"],"tags":["composio","authorize","direct-mode","validation","empty-string"],"backgroundTag":"empty-string-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}