tinyhumansai/openhuman · error · anyhow::Error

Backend error for DELETE {}: {}

Error message

Backend error for DELETE {}: {}

What it means

The envelope-error twin of the non-2xx case: the DELETE returned 2xx but the parsed {success, data, error} envelope has success=false. The message relays the backend's error string (or "unknown backend error"), after routing through the observability classifier so known user-state failures (toolkit not enabled, trigger type not found, missing required fields — the OPENHUMAN-TAURI-3R/-3S/-34/-97 class) demote to breadcrumbs instead of Sentry events.

Source

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

        let envelope: Envelope<T> = resp.json().await?;
        if !envelope.success {
            let msg = envelope
                .error
                .unwrap_or_else(|| "unknown backend error".into());
            // Mirrors the integrations envelope-error sites — route through
            // the observability classifier so user-state envelope failures
            // (composio "Toolkit X is not enabled" / "Trigger type …
            // not found" / "Missing required fields: …" — OPENHUMAN-TAURI-3R
            // / -3S / -34 / -97) demote to a breadcrumb instead of firing
            // a Sentry event. Genuine backend bugs still surface.
            crate::core::observability::report_error_or_expected(
                msg.as_str(),
                "composio",
                "delete",
                &[("path", path), ("failure", "envelope_error")],
            );
            anyhow::bail!("Backend error for DELETE {}: {}", url, msg);
        }
        envelope.data.ok_or_else(|| {
            anyhow::anyhow!("Backend returned success but no data for DELETE {}", url)
        })
    }
}

fn is_post_oauth_auth_readiness_error(resp: &ComposioExecuteResponse) -> bool {
    if resp.successful {
        return false;
    }
    let Some(error) = resp.error.as_deref() else {
        return false;
    };
    let normalized = error.trim().to_ascii_lowercase();
    POST_OAUTH_AUTH_ERROR_STRINGS
        .iter()
        .any(|needle| normalized.contains(needle))

View on GitHub (pinned to 7491200858)

Solutions

  1. Read the trailing {msg} — it is the backend's literal error string naming the refused operation
  2. If it says the toolkit/trigger is not enabled or not found, refresh state and stop offering the delete
  3. Capture the raw envelope in debug logging when the message is "unknown backend error" to detect contract drift
Defensive patterns

Strategy: try-catch

Try / catch

match client.delete_connection(id).await {
    Ok(resp) => Ok(resp),
    Err(err) => {
        let msg = err.to_string();
        if let Some(rest) = msg.strip_prefix("Backend error for DELETE ") {
            let backend_msg = rest.rsplit(": ").next().unwrap_or(rest);
            if backend_msg.contains("not enabled") || backend_msg.contains("not found") {
                return refresh_connections_and_accept(id); // user-state drift, not a bug
            }
        }
        Err(err)
    }
}

Prevention

When it happens

Trigger: DELETE /agent-integrations/composio/connections/{id} or /triggers/{id} answering 2xx with {success:false, error:...} — e.g. Composio reporting "Toolkit X is not enabled", "Trigger type ... not found", or "Missing required fields" inside a 200 response.

Common situations: Backend started packaging user-state failures in a 2xx envelope; toolkit disabled server-side between listing and deleting; contract change where the error field went missing (message then reads "unknown backend error").

Related errors


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