tinyhumansai/openhuman · error

{} {} failed ({status}): {text}

Error message

{} {} failed ({status}): {text}

What it means

The generic non-2xx failure bail inside authed_json: "{method} {path} failed ({status}): {body}" for any response that is not 2xx and not specially classified (401 session-expiry, channel-message 404, announcements-latest 404 have their own typed paths). It carries the raw status and response text, so the root cause must be read from those. Transient statuses are logged, not Sentry-reported; this bail is the catch-all remainder.

Source

Thrown at src/api/rest.rs:848

                        "{} {} failed ({status}); response_body_len={}; body_shape={}",
                        method.as_str(),
                        url.path(),
                        text.len(),
                        body_shape,
                    )
                    .as_str(),
                    "backend_api",
                    "authed_json",
                    &[
                        ("method", method.as_str()),
                        ("path", url.path()),
                        ("host", host),
                        ("status", status_str.as_str()),
                        ("failure", "non_2xx"),
                    ],
                );
            }
            anyhow::bail!(
                "{} {} failed ({status}): {text}",
                method.as_str(),
                url.path()
            );
        }
    }

    /// Lists all active integrations for the current user.
    pub async fn list_integrations(&self, bearer_jwt: &str) -> Result<Vec<IntegrationSummary>> {
        let value = self
            .authed_json(bearer_jwt, Method::GET, "auth/integrations", None)
            .await?;
        let integrations = value
            .get("integrations")
            .cloned()
            .unwrap_or_else(|| value.clone());
        serde_json::from_value(integrations).context("parse integrations response")
    }

View on GitHub (pinned to a221052e0d)

Solutions

  1. Read the status and text embedded in the message — they name the actual backend complaint
  2. 404: confirm the route exists on the deployed backend version and the path/params are correct
  3. 400/422: diff your request body against the backend schema; usually field-name or type drift
  4. 429/5xx: retry with backoff (transient by design); check backend health before retrying hard

Example fix

// before
let v = client.authed_json(&jwt, Method::POST, path, Some(body)).await?;

// after
let v = match client.authed_json(&jwt, Method::POST, path, Some(body)).await {
    Ok(v) => v,
    Err(err) => {
        let msg = err.to_string();
        if msg.contains("failed (429)") || msg.contains("failed (5") {
            tokio::time::sleep(Duration::from_secs(2)).await;
            client.authed_json(&jwt, Method::POST, path, Some(body)).await?
        } else {
            return Err(err.context(format!("call {path} rejected")));
        }
    }
};
Defensive patterns

Strategy: retry

Type guard

fn status_from_err(err: &anyhow::Error) -> Option<u16> {
    let msg = err.to_string();
    let mid = msg.split("failed (").nth(1)?;
    mid.split(')').next()?.parse().ok()
}

Try / catch

const MAX_ATTEMPTS: u32 = 3;
for attempt in 1..=MAX_ATTEMPTS {
    match client.authed_json(&jwt, method, path, body.clone()).await {
        Ok(v) => break Ok(v),
        Err(err) => match status_from_err(&err) {
            Some(429) | Some(500..=599) if attempt < MAX_ATTEMPTS => {
                tokio::time::sleep(Duration::from_millis(250 * 2u64.pow(attempt - 1))).await;
            }
            _ => break Err(err.context(format!("{path} rejected"))),
        },
    }
}?;

Prevention

When it happens

Trigger: Any authed_json / SDK call hitting 400 (bad body), 403 (forbidden), 404 on an unclassified route, 409, 429, or 5xx — e.g. sending a malformed integration payload, calling a route the backend version does not have, or a backend outage.

Common situations: Expired-but-not-401 credentials on a scoped route, request body schema drift after a backend deploy, rate limiting on bursty polling, backend 502/503 during deploys, or pointing the client at the wrong environment (staging route missing in prod).

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/5a6c43b352de8bc5. Report an issue: GitHub.