{"record":{"id":"4940e436d3ead920","repo":"neondatabase/neon","slug":"unexpected-message-type-msg","errorCode":null,"errorMessage":"unexpected message type: {msg:?}","messagePattern":"unexpected message type: (.+?)","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"libs/postgres_backend/src/lib.rs","lineNumber":842,"sourceCode":"                // ReadyForQuery message and backend just switches back to\n                // processing mode after sending CopyDone or ErrorResponse.\n            }\n\n            FeMessage::Sync => {\n                self.write_message_noflush(&BeMessage::ReadyForQuery)?;\n            }\n\n            FeMessage::Terminate => {\n                return Ok(ProcessMsgResult::Break);\n            }\n\n            // We prefer explicit pattern matching to wildcards, because\n            // this helps us spot the places where new variants are missing\n            FeMessage::CopyData(_)\n            | FeMessage::CopyDone\n            | FeMessage::CopyFail\n            | FeMessage::PasswordMessage(_) => {\n                return Err(QueryError::Other(anyhow::anyhow!(\n                    \"unexpected message type: {msg:?}\",\n                )));\n            }\n        }\n\n        Ok(ProcessMsgResult::Continue)\n    }\n\n    /// - Log as info/error result of handling COPY stream and send back\n    ///   ErrorResponse if that makes sense.\n    /// - Shutdown the stream if we got Terminate.\n    /// - Then close the connection because we don't handle exiting from COPY\n    ///   stream normally.\n    pub async fn handle_copy_stream_end(&mut self, end: CopyStreamHandlerEnd) {\n        use CopyStreamHandlerEnd::*;\n\n        let expected_end = match &end {\n            ServerInitiated(_) | CopyDone | CopyFail | Terminate | EOF | Cancelled => true,","sourceCodeStart":824,"sourceCodeEnd":860,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/postgres_backend/src/lib.rs#L824-L860","documentation":"process_message in the Established state accepts Query/Parse/Describe/Bind/Execute/Sync/Flush/Terminate; the message kinds CopyData, CopyDone, CopyFail, and PasswordMessage are explicitly matched to this error. The exhaustive match is deliberate so that new FeMessage variants force a review of every handler -- a COPY-phase message or a password message outside its phase is a protocol-state violation.","triggerScenarios":"A client sends CopyData/CopyDone/CopyFail outside an active COPY stream (e.g. after an error aborted COPY but the client kept streaming), or sends a PasswordMessage after authentication already completed (re-auth mid-session is not supported).","commonSituations":"Clients that ignore a mid-COPY ErrorResponse and keep piping COPY frames; drivers attempting SASL/password re-authentication on the same session after a timeout; protocol fuzzers; state machine bugs in custom proxies that reorder frames.","solutions":["On the client, abort the COPY stream as soon as an ErrorResponse arrives: send CopyFail/Terminate, do not continue sending CopyData","Do not send PasswordMessage after auth; open a new connection instead of re-authenticating in place","If proxying, keep COPY frames strictly within the COPY both-mode window reported by the server","Log msg {:?} to identify which frame was mis-sequenced and by which code path"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match backend.run(&mut handler).await {\n    Err(QueryError::Other(e))\n        if e.to_string().contains(\"unexpected message type\") =>\n    {\n        // Client violated protocol phase (COPY frame or password outside its window).\n        // Send ErrorResponse (already done by the backend), log which FeMessage it was,\n        // and close -- the stream state cannot be trusted afterwards.\n        tracing::warn!(peer = ?peer, error = %e, \"protocol phase violation; closing\");\n    }\n    other => other?,\n}","preventionTips":["Clients: upon any ErrorResponse during COPY, stop sending CopyData and finish with CopyFail or Terminate","Never attempt re-authentication on an established session; reconnect instead","In proxies, track COPY mode per connection and drop COPY frames routed to non-COPY backends","Fuzz your client's error paths: error-during-COPY is the classic trigger for this violation"],"tags":["rust","postgres-protocol","copy-protocol","state-machine","validation"],"backgroundTag":"postgres-protocol-state-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}