{"record":{"id":"5a6c43b352de8bc5","repo":"tinyhumansai/openhuman","slug":"failed-status-text","errorCode":null,"errorMessage":"{} {} failed ({status}): {text}","messagePattern":"(.+?) (.+?) failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/rest.rs","lineNumber":848,"sourceCode":"                        \"{} {} failed ({status}); response_body_len={}; body_shape={}\",\n                        method.as_str(),\n                        url.path(),\n                        text.len(),\n                        body_shape,\n                    )\n                    .as_str(),\n                    \"backend_api\",\n                    \"authed_json\",\n                    &[\n                        (\"method\", method.as_str()),\n                        (\"path\", url.path()),\n                        (\"host\", host),\n                        (\"status\", status_str.as_str()),\n                        (\"failure\", \"non_2xx\"),\n                    ],\n                );\n            }\n            anyhow::bail!(\n                \"{} {} failed ({status}): {text}\",\n                method.as_str(),\n                url.path()\n            );\n        }\n    }\n\n    /// Lists all active integrations for the current user.\n    pub async fn list_integrations(&self, bearer_jwt: &str) -> Result<Vec<IntegrationSummary>> {\n        let value = self\n            .authed_json(bearer_jwt, Method::GET, \"auth/integrations\", None)\n            .await?;\n        let integrations = value\n            .get(\"integrations\")\n            .cloned()\n            .unwrap_or_else(|| value.clone());\n        serde_json::from_value(integrations).context(\"parse integrations response\")\n    }","sourceCodeStart":830,"sourceCodeEnd":866,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L830-L866","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Read the status and text embedded in the message — they name the actual backend complaint","404: confirm the route exists on the deployed backend version and the path/params are correct","400/422: diff your request body against the backend schema; usually field-name or type drift","429/5xx: retry with backoff (transient by design); check backend health before retrying hard"],"exampleFix":"// before\nlet v = client.authed_json(&jwt, Method::POST, path, Some(body)).await?;\n\n// after\nlet v = match client.authed_json(&jwt, Method::POST, path, Some(body)).await {\n    Ok(v) => v,\n    Err(err) => {\n        let msg = err.to_string();\n        if msg.contains(\"failed (429)\") || msg.contains(\"failed (5\") {\n            tokio::time::sleep(Duration::from_secs(2)).await;\n            client.authed_json(&jwt, Method::POST, path, Some(body)).await?\n        } else {\n            return Err(err.context(format!(\"call {path} rejected\")));\n        }\n    }\n};","handlingStrategy":"retry","validationCode":null,"typeGuard":"fn status_from_err(err: &anyhow::Error) -> Option<u16> {\n    let msg = err.to_string();\n    let mid = msg.split(\"failed (\").nth(1)?;\n    mid.split(')').next()?.parse().ok()\n}","tryCatchPattern":"const MAX_ATTEMPTS: u32 = 3;\nfor attempt in 1..=MAX_ATTEMPTS {\n    match client.authed_json(&jwt, method, path, body.clone()).await {\n        Ok(v) => break Ok(v),\n        Err(err) => match status_from_err(&err) {\n            Some(429) | Some(500..=599) if attempt < MAX_ATTEMPTS => {\n                tokio::time::sleep(Duration::from_millis(250 * 2u64.pow(attempt - 1))).await;\n            }\n            _ => break Err(err.context(format!(\"{path} rejected\"))),\n        },\n    }\n}?;","preventionTips":["Read the embedded status+text before choosing a remedy; 4xx (except 429) will not fix themselves","Wrap bursty polling with jittered backoff to avoid 429s","Log method/path/status together (as authed_json already does) so failures triage fast"],"tags":["rust","backend-api","http-status","catch-all","retry"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}