{"record":{"id":"8fb409c06c54a329","repo":"tinyhumansai/openhuman","slug":"composio-direct-api-key-must-not-be-empty","errorCode":null,"errorMessage":"composio direct api key must not be empty","messagePattern":"composio direct api key must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":730,"sourceCode":"/// HMAC-verified trigger fan-out, no `/agent-integrations/pricing`),\n/// so most existing call-sites continue to use `Backend` for now.\n/// Direct-mode integration of the full surface (especially trigger\n/// webhooks) is a follow-up.\npub enum ComposioClientKind {\n    Backend(ComposioClient),\n    /// Held inside an `Arc` so the variant stays cheap to clone — this\n    /// matches the rest of the tool registry which juggles\n    /// `Arc<dyn Tool>` for the same direct-mode tool elsewhere.\n    Direct(Arc<crate::openhuman::tools::ComposioTool>),\n}\n\npub(crate) fn create_direct_composio_tool_for_api_key(\n    config: &crate::openhuman::config::Config,\n    api_key: &str,\n) -> anyhow::Result<Arc<crate::openhuman::tools::ComposioTool>> {\n    let api_key = api_key.trim();\n    if api_key.is_empty() {\n        anyhow::bail!(\"composio direct api key must not be empty\");\n    }\n\n    // The direct client takes a `SecurityPolicy` for `Tool::execute`\n    // gating, but the factory's job is only to materialize a *client*\n    // — it does not actually invoke `execute()` itself, so the\n    // default policy is sufficient here. Callers that go through\n    // the `Tool` surface re-acquire the live policy from their own\n    // context.\n    let security = Arc::new(crate::openhuman::security::SecurityPolicy::default());\n    #[cfg(debug_assertions)]\n    let tool = match (\n        std::env::var(\"OPENHUMAN_COMPOSIO_DIRECT_BASE_V2\").ok(),\n        std::env::var(\"OPENHUMAN_COMPOSIO_DIRECT_BASE_V3\").ok(),\n    ) {\n        (Some(base_v2), Some(base_v3)) => {\n            crate::openhuman::tools::ComposioTool::new_with_base_urls_for_loopback(\n                api_key,\n                Some(config.composio.entity_id.as_str()),","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L712-L748","documentation":"create_direct_composio_tool_for_api_key builds the direct-to-Composio (non-backend-proxied) tool from a user-supplied API key; a key that is empty after trimming is rejected before any client is constructed. The guard sits at factory time so direct mode never starts with a credential that cannot work.","triggerScenarios":"Direct Composio mode enabled while the configured api_key is empty or whitespace — e.g. the key env var unset in CI but the config still selects direct mode, or a blank key saved from settings.","commonSituations":"Environment variable for the key missing in CI/containers; UI allowed saving an empty key; key field contains only whitespace from a bad copy/paste; config default of \"\" left in place.","solutions":["Set the real Composio API key in the config/environment before enabling direct mode","Validate the key non-empty at config-load time with a message naming the exact setting, instead of failing later at tool creation","If the key is genuinely absent, stay on the backend-proxied path rather than selecting direct mode"],"exampleFix":"// before\nlet tool = create_direct_composio_tool_for_api_key(&config, cfg.composio_api_key.as_str()).await?;\n\n// after — validate at config load with a named setting\nlet key = cfg.composio_api_key.trim();\nif key.is_empty() {\n    anyhow::bail!(\"composio direct mode requires composio_api_key to be set\");\n}\nlet tool = create_direct_composio_tool_for_api_key(&config, key)?;","handlingStrategy":"validation","validationCode":"let api_key = cfg.composio_api_key.trim();\nif api_key.is_empty() {\n    anyhow::bail!(\n        \"composio direct mode requires a non-empty API key (setting: composio_api_key / env var)\"\n    );\n}\nlet tool = create_direct_composio_tool_for_api_key(&config, api_key)?;","typeGuard":"fn api_key_present(cfg: &Config) -> bool {\n    !cfg.composio_api_key.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Validate the key at config load with a message naming the setting and env var","Fail CI/startup checks when direct mode is selected without a key instead of deferring to first tool creation","Trim pasted keys on save in the UI and reject whitespace-only input"],"tags":["composio","api-key","configuration","empty-string","credentials"],"backgroundTag":"missing-api-key","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-23T11:17:13.642Z"}