{"record":{"id":"0cdf7701577188d3","repo":"tinyhumansai/openhuman","slug":"composio-disable-trigger-triggerid-must-not-be-em","errorCode":null,"errorMessage":"composio.disable_trigger: triggerId must not be empty","messagePattern":"composio\\.disable_trigger: triggerId must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":459,"sourceCode":"        }\n        let mut body = json!({ \"connectionId\": connection_id, \"slug\": slug });\n        if let Some(config) = trigger_config {\n            body[\"triggerConfig\"] = config;\n        }\n        tracing::debug!(slug = %slug, connection_id = %connection_id, \"[composio] enable_trigger\");\n        self.inner\n            .post::<ComposioEnableTriggerResponse>(\"/agent-integrations/composio/triggers\", &body)\n            .await\n    }\n\n    /// `DELETE /agent-integrations/composio/triggers/:triggerId`.\n    pub async fn disable_trigger(\n        &self,\n        trigger_id: &str,\n    ) -> Result<ComposioDisableTriggerResponse> {\n        let trigger_id = trigger_id.trim();\n        if trigger_id.is_empty() {\n            anyhow::bail!(\"composio.disable_trigger: triggerId must not be empty\");\n        }\n        tracing::debug!(trigger_id = %trigger_id, \"[composio] disable_trigger\");\n        self.raw_delete::<ComposioDisableTriggerResponse>(&format!(\n            \"/agent-integrations/composio/triggers/{}\",\n            urlencoding::encode(trigger_id)\n        ))\n        .await\n    }\n\n    // ── Raw DELETE ──────────────────────────────────────────────────\n\n    /// Perform an HTTP DELETE and parse the standard backend envelope.\n    ///\n    /// [`IntegrationClient`] only exposes `get` / `post` today, and the\n    /// composio route actually requires a DELETE. We re-implement the\n    /// envelope handling here so we don't have to widen the shared\n    /// client's public surface just for one caller.\n    async fn raw_delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L441-L477","documentation":"ComposioClient::disable_trigger rejects a triggerId that is empty after trimming, before the DELETE /agent-integrations/composio/triggers/{triggerId} URL is built. The triggerId is the instance id returned by create/enable_trigger — not the slug — and must not be blank or the DELETE hits a malformed URL.","triggerScenarios":"Calling disable_trigger(\"\") — usually a toggle fired from a row whose instance id is missing, or a caller passing the trigger slug where the instance id belongs.","commonSituations":"UI row keyed by slug instead of triggerId; instance id lost after re-fetching the enabled-trigger list; double-toggle racing a list refresh that cleared the row.","solutions":["Carry the triggerId returned by create_trigger/enable_trigger through to the disable call — do not substitute the slug","Guard the toggle handler on a non-empty instance id and refresh the list when it is blank","Disable the UI toggle until the row's id is populated"],"exampleFix":"// before\nclient.disable_trigger(row.slug.as_str()).await?;\n\n// after\nlet Some(id) = row.trigger_id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {\n    anyhow::bail!(\"row has no trigger instance id; refresh enabled triggers\");\n};\nclient.disable_trigger(id).await?;","handlingStrategy":"validation","validationCode":"let Some(id) = trigger_id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {\n    anyhow::bail!(\"trigger instance id is required to disable a trigger\");\n};\nclient.disable_trigger(id).await?;","typeGuard":"fn is_non_empty_id(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Carry the triggerId returned by create/enable_trigger through to disable — never substitute the slug","Key UI rows by instance id, not slug, so the disable handler always has the right value","Refresh the enabled-trigger list when a row's id is missing instead of firing the delete"],"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"}