tinyhumansai/openhuman · error · anyhow::Error

composio.delete_connection: connectionId must not be empty

Error message

composio.delete_connection: connectionId must not be empty

What it means

ComposioClient::delete_connection rejects a connectionId that is empty after trimming, before issuing DELETE /agent-integrations/composio/connections/{id}. The id must come from a previous authorize or list_connections result; the guard stops a meaningless DELETE against the bare /connections/ URL.

Source

Thrown at src/openhuman/integrations/composio/client.rs:126

                obj.insert(k.clone(), v.clone());
            }
        }
        merge_required_oauth_scopes(&mut body, toolkit)?;
        self.inner
            .post::<ComposioAuthorizeResponse>("/agent-integrations/composio/authorize", &body)
            .await
    }

    /// `DELETE /agent-integrations/composio/connections/{id}`.
    ///
    /// The backend verifies that the caller owns the connection before
    /// deleting it. We call this via `POST` with a synthetic `_method`
    /// body because [`IntegrationClient`] does not currently expose a
    /// generic `delete()` — the backend accepts the method override.
    pub async fn delete_connection(&self, connection_id: &str) -> Result<ComposioDeleteResponse> {
        let connection_id = connection_id.trim();
        if connection_id.is_empty() {
            anyhow::bail!("composio.delete_connection: connectionId must not be empty");
        }
        tracing::debug!(connection_id = %connection_id, "[composio] delete_connection");
        // Fall through to the reusable raw HTTP delete helper below.
        self.raw_delete::<ComposioDeleteResponse>(&format!(
            "/agent-integrations/composio/connections/{connection_id}"
        ))
        .await
    }

    // ── Tools ───────────────────────────────────────────────────────

    /// `GET /agent-integrations/composio/tools?toolkits=<csv>&tags=<csv>` — fetch
    /// OpenAI function-calling schemas. Omit `toolkits` to get every enabled
    /// toolkit's tools. `tags` narrows by Composio action tag (OR semantics —
    /// multiple tags broaden the result).
    pub async fn list_tools(
        &self,
        toolkits: Option<&[String]>,

View on GitHub (pinned to 7491200858)

Solutions

  1. Fetch the connection list first and delete by an id taken from it
  2. Make the id a required parameter in the calling RPC/UI path so an empty value is caught with a better message
  3. Disable the delete action in the UI when the row has no id

Example fix

// before
client.delete_connection(conn.id.as_deref().unwrap_or("")).await?;

// after
let Some(id) = conn.id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {
    anyhow::bail!("connection has no id; refresh the connection list");
};
client.delete_connection(id).await?;
Defensive patterns

Strategy: validation

Validate before calling

let Some(id) = connection_id.as_deref().map(str::trim).filter(|s| !s.is_empty()) else {
    anyhow::bail!("connection id is required to delete a connection");
};
client.delete_connection(id).await?;

Type guard

fn is_non_empty_id(s: &str) -> bool {
    !s.trim().is_empty()
}

Prevention

When it happens

Trigger: Calling delete_connection("") or with a whitespace-only id — usually a delete fired from a UI row whose connection id field was never populated, or a flow that deletes before fetching connections.

Common situations: Frontend row model missing the id mapping; deleting from a stale/empty connections list; race where the row was cleared before the delete handler ran.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/0f4f144323b5450c. Report an issue: GitHub.