tinyhumansai/openhuman · error · anyhow::Error
composio.disable_trigger: triggerId must not be empty
Error message
composio.disable_trigger: triggerId must not be empty
What it means
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.
Source
Thrown at src/openhuman/integrations/composio/client.rs:459
}
let mut body = json!({ "connectionId": connection_id, "slug": slug });
if let Some(config) = trigger_config {
body["triggerConfig"] = config;
}
tracing::debug!(slug = %slug, connection_id = %connection_id, "[composio] enable_trigger");
self.inner
.post::<ComposioEnableTriggerResponse>("/agent-integrations/composio/triggers", &body)
.await
}
/// `DELETE /agent-integrations/composio/triggers/:triggerId`.
pub async fn disable_trigger(
&self,
trigger_id: &str,
) -> Result<ComposioDisableTriggerResponse> {
let trigger_id = trigger_id.trim();
if trigger_id.is_empty() {
anyhow::bail!("composio.disable_trigger: triggerId must not be empty");
}
tracing::debug!(trigger_id = %trigger_id, "[composio] disable_trigger");
self.raw_delete::<ComposioDisableTriggerResponse>(&format!(
"/agent-integrations/composio/triggers/{}",
urlencoding::encode(trigger_id)
))
.await
}
// ── Raw DELETE ──────────────────────────────────────────────────
/// Perform an HTTP DELETE and parse the standard backend envelope.
///
/// [`IntegrationClient`] only exposes `get` / `post` today, and the
/// composio route actually requires a DELETE. We re-implement the
/// envelope handling here so we don't have to widen the shared
/// client's public surface just for one caller.
async fn raw_delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {View on GitHub (pinned to 7491200858)
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
Example fix
// before
client.disable_trigger(row.slug.as_str()).await?;
// after
let Some(id) = row.trigger_id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {
anyhow::bail!("row has no trigger instance id; refresh enabled triggers");
};
client.disable_trigger(id).await?; Defensive patterns
Strategy: validation
Validate before calling
let Some(id) = trigger_id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {
anyhow::bail!("trigger instance id is required to disable a trigger");
};
client.disable_trigger(id).await?; Type guard
fn is_non_empty_id(s: &str) -> bool {
!s.trim().is_empty()
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- composio.create_trigger: slug must not be empty
- composio.list_available_triggers: toolkit must not be empty
- composio.enable_trigger: connectionId must not be empty
- composio.enable_trigger: slug must not be empty
- composio.execute_tool: tool slug must not be empty
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/0cdf7701577188d3.
Report an issue: GitHub.