{"record":{"id":"0ba60604f5c982fe","repo":"tinyhumansai/openhuman","slug":"composio-list-available-triggers-toolkit-must-not","errorCode":null,"errorMessage":"composio.list_available_triggers: toolkit must not be empty","messagePattern":"composio\\.list_available_triggers: toolkit must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":389,"sourceCode":"        tracing::debug!(slug = %slug, \"[composio] create_trigger\");\n        self.inner\n            .post::<ComposioCreateTriggerResponse>(\"/agent-integrations/composio/triggers\", &body)\n            .await\n    }\n\n    // ── Trigger management (PR #671) ────────────────────────────────\n\n    /// `GET /agent-integrations/composio/triggers/available` — catalog of\n    /// triggers the user could enable for a toolkit. For GitHub the\n    /// backend fans out into per-repo entries scoped by `connection_id`.\n    pub async fn list_available_triggers(\n        &self,\n        toolkit: &str,\n        connection_id: Option<&str>,\n    ) -> Result<ComposioAvailableTriggersResponse> {\n        let toolkit = toolkit.trim();\n        if toolkit.is_empty() {\n            anyhow::bail!(\"composio.list_available_triggers: toolkit must not be empty\");\n        }\n        let toolkit_q = urlencoding::encode(toolkit);\n        let path = match connection_id.map(str::trim).filter(|id| !id.is_empty()) {\n            Some(id) => format!(\n                \"/agent-integrations/composio/triggers/available?toolkit={toolkit_q}&connectionId={}\",\n                urlencoding::encode(id)\n            ),\n            None => format!(\n                \"/agent-integrations/composio/triggers/available?toolkit={toolkit_q}\"\n            ),\n        };\n        tracing::debug!(path = %path, \"[composio] list_available_triggers\");\n        self.inner\n            .get::<ComposioAvailableTriggersResponse>(&path)\n            .await\n    }\n\n    /// `GET /agent-integrations/composio/triggers` — currently enabled","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L371-L407","documentation":"ComposioClient::list_available_triggers rejects a toolkit name that is empty after trimming, before building the GET /agent-integrations/composio/triggers/available?toolkit=... query. A blank toolkit would return a meaningless catalog (or a backend 400), so the client fails fast.","triggerScenarios":"Calling list_available_triggers(\"\", connection_id) — typically the trigger-picker UI loaded before a toolkit was selected, or a polling loop running with an unset toolkit field.","commonSituations":"Trigger picker screen rendered with no toolkit context; background 5 s poll started with empty state; toolkit field cleared while the picker stayed mounted.","solutions":["Skip the catalog call (render an empty picker) until a toolkit is selected","Guard the polling loop on toolkit non-emptiness so it does not fire with empty state","Pass a connection-derived toolkit when the UI is connection-scoped"],"exampleFix":"// before\nlet triggers = client.list_available_triggers(toolkit.as_str(), conn).await?;\n\n// after\nlet toolkit = toolkit.trim();\nlet triggers = if toolkit.is_empty() {\n    Vec::new()\n} else {\n    client.list_available_triggers(toolkit, conn).await?.data\n};","handlingStrategy":"validation","validationCode":"let toolkit = toolkit.trim();\nlet triggers = if toolkit.is_empty() {\n    Vec::new() // picker not ready — skip the catalog call\n} else {\n    client.list_available_triggers(toolkit, connection_id).await?.data\n};","typeGuard":"fn is_non_empty_toolkit(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Gate trigger-catalog polling on a non-empty toolkit instead of letting it fire with empty state","Render the picker in an empty/disabled state until the toolkit context is set","Derive the toolkit from the selected connection when the UI is connection-scoped"],"tags":["composio","triggers","validation","empty-string","argument-validation"],"backgroundTag":"empty-string-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}