{"record":{"id":"b530d0227479f85a","repo":"neondatabase/neon","slug":"unexpected-cancelrequest-message-during-handshake","errorCode":null,"errorMessage":"Unexpected CancelRequest message during handshake","messagePattern":"Unexpected CancelRequest message during handshake","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"warning","filePath":"libs/postgres_backend/src/lib.rs","lineNumber":723,"sourceCode":"                    AuthType::Trust => {\n                        self.write_message_noflush(&BeMessage::AuthenticationOk)?\n                            .write_message_noflush(&BeMessage::CLIENT_ENCODING)?\n                            .write_message_noflush(&BeMessage::INTEGER_DATETIMES)?\n                            // The async python driver requires a valid server_version\n                            .write_message_noflush(&BeMessage::server_version(\"14.1\"))?\n                            .write_message(&BeMessage::ReadyForQuery)\n                            .await?;\n                        self.state = ProtoState::Established;\n                    }\n                    AuthType::NeonJWT => {\n                        self.write_message(&BeMessage::AuthenticationCleartextPassword)\n                            .await?;\n                        self.state = ProtoState::Authentication;\n                    }\n                }\n            }\n            FeStartupPacket::CancelRequest { .. } => {\n                return Err(QueryError::Other(anyhow::anyhow!(\n                    \"Unexpected CancelRequest message during handshake\"\n                )));\n            }\n        }\n        Ok(())\n    }\n\n    // Proto looks like this:\n    // FeMessage::Query(\"pagestream_v2{FeMessage::CopyData(PagesetreamFeMessage::GetPage(..))}\")\n\n    async fn process_message(\n        &mut self,\n        handler: &mut impl Handler<IO>,\n        msg: FeMessage,\n        unnamed_query_string: &mut Bytes,\n    ) -> Result<ProcessMsgResult, QueryError> {\n        // Allow only startup and password messages during auth. Otherwise client would be able to bypass auth\n        // TODO: change that to proper top-level match of protocol state with separate message handling for each state","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/postgres_backend/src/lib.rs#L705-L741","documentation":"process_startup_message handles SSLRequest, GssEncRequest, and StartupMessage during the handshake; a FeStartupPacket::CancelRequest arriving there is rejected with QueryError::Other. Cancel requests are expected on their own dedicated connection carrying (pid, secret key) after a normal startup -- receiving one before the handshake completes is a protocol-order violation this backend does not support.","triggerScenarios":"A client opening a connection and sending CancelRequest as the first (or a pre-handshake) packet -- e.g. a cancel racing with connection setup, a proxy multiplexing cancel frames onto an incomplete connection, or a test harness firing pg_cancel immediately after connect.","commonSituations":"Statement-timeout cancellation racing connection establishment under load; connection poolers forwarding cancel requests on not-yet-established links; custom drivers that send the cancel packet too eagerly; scanners probing protocol behavior.","solutions":["Send cancel requests only over a fully established connection (open a new one and wait for startup, then CancelRequest)","Rate-limit or debounce aggressive cancel logic on the client (timers firing before connect completes)","If you proxy postgres traffic, do not multiplex CancelRequest onto connections still in handshake","Treat the error as connection-fatal: close and reconnect rather than retrying on the same socket"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match backend.handshake(&mut handler).await {\n    Err(QueryError::Other(e))\n        if e.to_string().contains(\"Unexpected CancelRequest\") =>\n    {\n        // Benign client behavior quirk (cancel raced with startup).\n        // Close quietly with a debug log; do not alert, do not retry on this connection.\n        tracing::debug!(peer = ?peer_addr, \"cancel request during handshake; dropping connection\");\n    }\n    other => other?,\n}","preventionTips":["Client-side: only send CancelRequest on a connection that has completed startup (or a fresh dedicated connection)","Debounce cancel timers so they cannot fire before the connection reaches Established under slow networks","Proxies: route cancel packets only to already-established backends, never to handshaking ones","Classify this error as noise in alerting rules to avoid pages for harmless client races"],"tags":["rust","postgres-protocol","handshake","cancel-request"],"backgroundTag":"postgres-protocol-handshake-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}