tinyhumansai/openhuman · error · anyhow::Error
composio.list_available_triggers: toolkit must not be empty
Error message
composio.list_available_triggers: toolkit must not be empty
What it means
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.
Source
Thrown at src/openhuman/integrations/composio/client.rs:389
tracing::debug!(slug = %slug, "[composio] create_trigger");
self.inner
.post::<ComposioCreateTriggerResponse>("/agent-integrations/composio/triggers", &body)
.await
}
// ── Trigger management (PR #671) ────────────────────────────────
/// `GET /agent-integrations/composio/triggers/available` — catalog of
/// triggers the user could enable for a toolkit. For GitHub the
/// backend fans out into per-repo entries scoped by `connection_id`.
pub async fn list_available_triggers(
&self,
toolkit: &str,
connection_id: Option<&str>,
) -> Result<ComposioAvailableTriggersResponse> {
let toolkit = toolkit.trim();
if toolkit.is_empty() {
anyhow::bail!("composio.list_available_triggers: toolkit must not be empty");
}
let toolkit_q = urlencoding::encode(toolkit);
let path = match connection_id.map(str::trim).filter(|id| !id.is_empty()) {
Some(id) => format!(
"/agent-integrations/composio/triggers/available?toolkit={toolkit_q}&connectionId={}",
urlencoding::encode(id)
),
None => format!(
"/agent-integrations/composio/triggers/available?toolkit={toolkit_q}"
),
};
tracing::debug!(path = %path, "[composio] list_available_triggers");
self.inner
.get::<ComposioAvailableTriggersResponse>(&path)
.await
}
/// `GET /agent-integrations/composio/triggers` — currently enabledView on GitHub (pinned to 7491200858)
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
Example fix
// before
let triggers = client.list_available_triggers(toolkit.as_str(), conn).await?;
// after
let toolkit = toolkit.trim();
let triggers = if toolkit.is_empty() {
Vec::new()
} else {
client.list_available_triggers(toolkit, conn).await?.data
}; Defensive patterns
Strategy: validation
Validate before calling
let toolkit = toolkit.trim();
let triggers = if toolkit.is_empty() {
Vec::new() // picker not ready — skip the catalog call
} else {
client.list_available_triggers(toolkit, connection_id).await?.data
}; Type guard
fn is_non_empty_toolkit(s: &str) -> bool {
!s.trim().is_empty()
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Trigger picker screen rendered with no toolkit context; background 5 s poll started with empty state; toolkit field cleared while the picker stayed mounted.
Related errors
- composio.create_trigger: slug must not be empty
- composio.enable_trigger: connectionId must not be empty
- composio.enable_trigger: slug must not be empty
- composio.disable_trigger: triggerId 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/0ba60604f5c982fe.
Report an issue: GitHub.