{"record":{"id":"543c9c9f7bdb0bb9","repo":"tinyhumansai/openhuman","slug":"backend-error-for","errorCode":null,"errorMessage":"Backend error for {} {}: {}","messagePattern":"Backend error for (.+?) (.+?): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/client.rs","lineNumber":492,"sourceCode":"    fn parse_envelope<T: serde::de::DeserializeOwned>(\n        method: &str,\n        path: &str,\n        url: &str,\n        value: serde_json::Value,\n    ) -> anyhow::Result<T> {\n        let method_upper = method.to_uppercase();\n        let envelope: BackendResponse<T> = serde_json::from_value(value)?;\n        if !envelope.success {\n            let msg = envelope\n                .error\n                .unwrap_or_else(|| \"unknown backend error\".into());\n            crate::core::observability::report_error_or_expected(\n                msg.as_str(),\n                \"integrations\",\n                method,\n                &[(\"path\", path), (\"failure\", \"envelope_error\")],\n            );\n            anyhow::bail!(\"Backend error for {} {}: {}\", method_upper, url, msg);\n        }\n        envelope.data.ok_or_else(|| {\n            anyhow::anyhow!(\n                \"Backend returned success but no data for {} {}\",\n                method_upper,\n                url\n            )\n        })\n    }\n\n    async fn request_json<T: serde::de::DeserializeOwned>(\n        &self,\n        method: reqwest::Method,\n        path: &str,\n        body: Option<&serde_json::Value>,\n    ) -> anyhow::Result<T> {\n        reject_backend_webhook_path(method.as_str(), path)?;\n        enforce_backend_egress(path)?;","sourceCodeStart":474,"sourceCodeEnd":510,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/client.rs#L474-L510","documentation":"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.","triggerScenarios":"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.","commonSituations":"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\").","solutions":["Read the trailing {msg} — it is the backend's own error string and names the actual cause","If the message indicates auth failure, re-authenticate / refresh the session and retry once","Verify the integration/toolkit is enabled and authorized for the account before calling its endpoints","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.post::<T>(path, &body).await {\n    Ok(data) => Ok(data),\n    Err(err) => {\n        let msg = err.to_string();\n        if let Some(rest) = msg.strip_prefix(\"Backend error for \") {\n            let backend_msg = rest.rsplit(\": \").next().unwrap_or(rest);\n            match backend_msg {\n                m if m.contains(\"not enabled\") => return enable_toolkit_then_retry().await,\n                m if m.contains(\"unauthorized\") || m.contains(\"session\") => return reauth_and_retry().await,\n                _ => {}\n            }\n        }\n        Err(err)\n    }\n}","preventionTips":["Authorize/enable the integration and verify session freshness before issuing integration calls","Log the full backend message with the path — the suffix is the only actionable part of the error","Treat 'unknown backend error' as a contract-drift signal and capture the raw body in debug logs"],"tags":["backend","integrations","http","envelope","api-error"],"backgroundTag":"api-error-response","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}