tinyhumansai/openhuman · error · anyhow::Error

Backend error for {} {}: {}

Error message

Backend error for {} {}: {}

What it means

Thrown when a backend response parses as the standard {success, data, error} envelope but success is false. IntegrationClient relays the backend's error string (or "unknown backend error" when the error field is absent) after routing it through the observability classifier with failure=envelope_error, which demotes known user-state errors (toolkit not enabled, missing fields) to breadcrumbs while genuine backend bugs still surface as Sentry events.

Source

Thrown at src/openhuman/integrations/client.rs:492

    fn parse_envelope<T: serde::de::DeserializeOwned>(
        method: &str,
        path: &str,
        url: &str,
        value: serde_json::Value,
    ) -> anyhow::Result<T> {
        let method_upper = method.to_uppercase();
        let envelope: BackendResponse<T> = serde_json::from_value(value)?;
        if !envelope.success {
            let msg = envelope
                .error
                .unwrap_or_else(|| "unknown backend error".into());
            crate::core::observability::report_error_or_expected(
                msg.as_str(),
                "integrations",
                method,
                &[("path", path), ("failure", "envelope_error")],
            );
            anyhow::bail!("Backend error for {} {}: {}", method_upper, url, msg);
        }
        envelope.data.ok_or_else(|| {
            anyhow::anyhow!(
                "Backend returned success but no data for {} {}",
                method_upper,
                url
            )
        })
    }

    async fn request_json<T: serde::de::DeserializeOwned>(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<&serde_json::Value>,
    ) -> anyhow::Result<T> {
        reject_backend_webhook_path(method.as_str(), path)?;
        enforce_backend_egress(path)?;

View on GitHub (pinned to 7491200858)

Solutions

  1. Read the trailing {msg} — it is the backend's own error string and names the actual cause
  2. If the message indicates auth failure, re-authenticate / refresh the session and retry once
  3. Verify the integration/toolkit is enabled and authorized for the account before calling its endpoints
  4. If the message is "unknown backend error", inspect the raw response body — the envelope lacked an error field, which can indicate a backend contract change
Defensive patterns

Strategy: try-catch

Try / catch

match client.post::<T>(path, &body).await {
    Ok(data) => Ok(data),
    Err(err) => {
        let msg = err.to_string();
        if let Some(rest) = msg.strip_prefix("Backend error for ") {
            let backend_msg = rest.rsplit(": ").next().unwrap_or(rest);
            match backend_msg {
                m if m.contains("not enabled") => return enable_toolkit_then_retry().await,
                m if m.contains("unauthorized") || m.contains("session") => return reauth_and_retry().await,
                _ => {}
            }
        }
        Err(err)
    }
}

Prevention

When it happens

Trigger: Any IntegrationClient.post/get where the backend answers {success:false, error:...} — e.g. calling a Composio endpoint for a toolkit that was never authorized, a missing required field, an expired session token, or an upstream provider failure relayed by the backend.

Common situations: Integration used before its toolkit was enabled in the UI; session token expired between calls; backend validation tightened after a version bump; envelope changed shape so the error field is missing (message then reads "unknown backend error").

Related errors


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