{"record":{"id":"e6542154dead6206","repo":"tinyhumansai/openhuman","slug":"backend-returned-status-for-delete-url-detai","errorCode":null,"errorMessage":"Backend returned {status} for DELETE {url}: {detail}","messagePattern":"Backend returned (.+?) for DELETE (.+?): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/integrations/composio/client.rs","lineNumber":545,"sourceCode":"                status,\n                logged_body\n            );\n            let status_str = status.as_u16().to_string();\n            // Mirrors the integrations post()/get() sites — see\n            // OPENHUMAN-TAURI-BC. 4xx user-input / auth-state shapes\n            // demote via the observability classifier; 5xx and\n            // non-transient 4xx still surface as actionable events.\n            crate::core::observability::report_error_or_expected(\n                format!(\"Backend returned {status} for DELETE {url}: {detail}\").as_str(),\n                \"composio\",\n                \"delete\",\n                &[\n                    (\"path\", path),\n                    (\"status\", status_str.as_str()),\n                    (\"failure\", \"non_2xx\"),\n                ],\n            );\n            anyhow::bail!(\"Backend returned {status} for DELETE {url}: {detail}\");\n        }\n\n        let envelope: Envelope<T> = resp.json().await?;\n        if !envelope.success {\n            let msg = envelope\n                .error\n                .unwrap_or_else(|| \"unknown backend error\".into());\n            // Mirrors the integrations envelope-error sites — route through\n            // the observability classifier so user-state envelope failures\n            // (composio \"Toolkit X is not enabled\" / \"Trigger type …\n            // not found\" / \"Missing required fields: …\" — OPENHUMAN-TAURI-3R\n            // / -3S / -34 / -97) demote to a breadcrumb instead of firing\n            // a Sentry event. Genuine backend bugs still surface.\n            crate::core::observability::report_error_or_expected(\n                msg.as_str(),\n                \"composio\",\n                \"delete\",\n                &[(\"path\", path), (\"failure\", \"envelope_error\")],","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/integrations/composio/client.rs#L527-L563","documentation":"raw_delete (shared by delete_connection and disable_trigger) checks the HTTP status of the DELETE against /agent-integrations/composio/...; any non-2xx bails with the status code, URL, and extracted detail body. The site first routes the failure through the observability classifier, so user-state 4xx (toolkit not enabled, trigger not found, missing fields) demote to breadcrumbs while 5xx and other 4xx stay actionable Sentry events.","triggerScenarios":"DELETE of a connection/trigger id that does not exist or was already deleted (404), a connection owned by another user (403), an expired session (401), or a backend 5xx. The URL and detail in the message identify which resource failed.","commonSituations":"Double-delete from a double-clicking UI or a retried request; deleting from a stale list after the resource vanished; concurrent deletion from another device/session.","solutions":["Read the status and {detail} suffix — they name the exact backend reason for the failed DELETE","Treat 404 as success when the goal is ensure-deleted (idempotent delete), since the end state is what you wanted","Refresh the connection/trigger list before offering delete actions so stale ids are not used","For 401, re-establish the session and retry once; for 5xx, retry with backoff"],"exampleFix":"// before\nclient.delete_connection(id).await?;\n\n// after — treat already-deleted as success\nmatch client.delete_connection(id).await {\n    Ok(resp) => Ok(resp),\n    Err(err) if err.to_string().contains(\"404\") => Ok(ComposioDeleteResponse::default()),\n    Err(err) => Err(err),\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.disable_trigger(trigger_id).await {\n    Ok(resp) => Ok(resp),\n    Err(err) => {\n        let msg = err.to_string();\n        if msg.contains(\" 404 \") || msg.contains(\" 410 \") {\n            Ok(ComposioDisableTriggerResponse::default()) // already gone — treat as deleted\n        } else if msg.contains(\" 401 \") {\n            reauth_and_retry_delete(trigger_id).await\n        } else {\n            Err(err) // 403 / 5xx — surface to the user\n        }\n    }\n}","preventionTips":["Make deletes idempotent: treat 404/410 as the desired end state","Refresh the resource list before offering delete actions so stale ids are not used","Debounce double-click delete buttons in the UI to avoid racing duplicate DELETEs"],"tags":["composio","http","delete","status-code","backend"],"backgroundTag":"http-error-status","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}