{"record":{"id":"49072eca0ae2e7b8","repo":"tinyhumansai/openhuman","slug":"channel-message-not-found-404-on","errorCode":null,"errorMessage":"channel message not found (404) on {} {}","messagePattern":"channel message not found \\(404\\) on (.+?) (.+?)","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"src/api/rest.rs","lineNumber":759,"sourceCode":"                // Defense-in-depth: DELETE 404s on any channel-message path that\n                // parse_message_path could not parse (e.g. exotic URL variant with extra\n                // segments). Still an expected backend state — suppress the Sentry event\n                // without propagating a typed error. Targets OPENHUMAN-TAURI-R7.\n                // PATCH is handled above and returns the typed\n                // `ChannelEditUnsupported` for both the parsed and unparsed shapes.\n                if method == Method::DELETE\n                    && url.path().contains(\"/channels/\")\n                    && url.path().contains(\"/messages/\")\n                {\n                    tracing::debug!(\n                        domain = \"backend_api\",\n                        operation = \"authed_json\",\n                        \"[backend_api] channel-message 404 on {} {} — path not matched by \\\n                         parse_message_path, suppressing Sentry (TAURI-R7 defense-in-depth)\",\n                        method.as_str(),\n                        url.path(),\n                    );\n                    anyhow::bail!(\n                        \"channel message not found (404) on {} {}\",\n                        method.as_str(),\n                        url.path(),\n                    );\n                }\n\n                // 404 on `/announcements/latest` means \"no announcement\" for\n                // this best-effort, cosmetic feature — not a code bug. Surface\n                // a typed `BackendApiError::AnnouncementNotFound` so the caller\n                // (`announcements::ops::get_latest_announcement`) can degrade to\n                // `null` instead of propagating an error, without funneling the\n                // 404 into `report_error`. Targets `TAURI-RUST-HW0` / `TAURI-RUST-KHX`.\n                if method == Method::GET && is_announcements_latest_path(url.path()) {\n                    tracing::info!(\n                        domain = \"backend_api\",\n                        operation = \"authed_json\",\n                        \"[backend_api] announcement-not-found 404 on {} {} — surfacing typed error\",\n                        method.as_str(),","sourceCodeStart":741,"sourceCodeEnd":777,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/api/rest.rs#L741-L777","documentation":"Bailed by authed_json when a DELETE whose URL contains /channels/ and /messages/ came back 404 and the path was not matched by parse_message_path. This is a deliberate classification (TAURI-R7 defense-in-depth): the 404 is suppressed from Sentry and surfaced as a plain \"message not found\" error because an unmatched-route delete of an ephemeral message is expected, not a bug. Callers should treat it as \"already gone\", not as a hard failure.","triggerScenarios":"send_channel_delete targeting a thinking-indicator or ephemeral message the backend already purged, or a message_id whose route shape does not match parse_message_path (e.g. provider-native id format), yielding 404 on DELETE /channels/{ch}/messages/{id}.","commonSituations":"Cleanup of ephemeral messages racing with backend TTL purge, retrying a delete after the message was already deleted, or a channel whose backend routes were deployed later than the core.","solutions":["Treat this error as success for ephemeral-message cleanup — the goal (message absent) is achieved","If the message should exist, verify the message_id format against the backend's route and parse_message_path expectations","Do not wrap this specific 404 in retry loops; retrying a not-found delete cannot succeed"],"exampleFix":"// before\nclient.send_channel_delete(ch, msg_id, &jwt).await?; // hard error on 404\n\n// after\nif let Err(err) = client.send_channel_delete(ch, msg_id, &jwt).await {\n    if err.to_string().starts_with(\"channel message not found (404)\") {\n        tracing::debug!(\"message already absent: {msg_id}\");\n    } else {\n        return Err(err);\n    }\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"fn is_channel_message_not_found(err: &anyhow::Error) -> bool {\n    err.to_string().starts_with(\"channel message not found (404)\")\n}","tryCatchPattern":"if let Err(err) = client.send_channel_delete(ch, msg_id, &jwt).await {\n    if is_channel_message_not_found(&err) {\n        // Expected for ephemeral cleanup: the goal (message absent) is met.\n        tracing::debug!(domain = \"channel\", \"message already gone: {msg_id}\");\n    } else {\n        return Err(err);\n    }\n}","preventionTips":["Treat 404 on ephemeral-message delete as success in cleanup paths","Never put this delete in an automatic retry loop","Verify message_id formats against the backend route shape when deletes 404 on messages that should exist"],"tags":["rust","channel","http-404","ephemeral","degrade"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}