{"record":{"id":"08410c95d4c3eab8","repo":"neondatabase/neon","slug":"unexpected-message-while-waiting-for-handshak","errorCode":null,"errorMessage":"Unexpected message {:?} while waiting for handshake","messagePattern":"Unexpected message (.+?) while waiting for handshake","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"libs/postgres_backend/src/lib.rs","lineNumber":635,"sourceCode":"\n                    let (_, jwt_response) = m.split_last().context(\"protocol violation\")?;\n\n                    if let Err(e) = handler.check_auth_jwt(self, jwt_response) {\n                        self.write_message_noflush(&BeMessage::ErrorResponse(\n                            &short_error(&e),\n                            Some(e.pg_error_code()),\n                        ))?;\n                        return Err(e);\n                    }\n\n                    self.write_message_noflush(&BeMessage::AuthenticationOk)?\n                        .write_message_noflush(&BeMessage::CLIENT_ENCODING)?\n                        .write_message(&BeMessage::ReadyForQuery)\n                        .await?;\n                    self.state = ProtoState::Established;\n                }\n                Some(m) => {\n                    return Err(QueryError::Other(anyhow::anyhow!(\n                        \"Unexpected message {:?} while waiting for handshake\",\n                        m\n                    )));\n                }\n                None => {\n                    trace!(\n                        \"postgres backend to {:?} received EOF during auth\",\n                        self.peer_addr\n                    );\n                    self.state = ProtoState::Closed;\n                    return Err(QueryError::Disconnected(ConnectionError::Protocol(\n                        ProtocolError::Protocol(\"EOF during auth\".to_string()),\n                    )));\n                }\n            }\n        }\n\n        Ok(())","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/postgres_backend/src/lib.rs#L617-L653","documentation":"During the postgres wire-protocol handshake, after the server has sent AuthenticationCleartextPassword (NeonJWT auth), the next message must be a PasswordMessage carrying the JWT. If some other FeMessage arrives (Query, Terminate, a second startup packet, ...), PostgresBackend raises QueryError::Other with this message; the connection is unusable afterwards.","triggerScenarios":"A client that skips the password exchange and sends a Query directly after startup; a client restarting the handshake (new SSLRequest/GssEncRequest) while in the Authentication state; a broken or non-libpq client speaking the wrong message order; protocol desync after partial writes.","commonSituations":"Custom drivers or test harnesses that assume trust auth on an endpoint requiring JWT; pgbouncers or proxies re-injecting startup packets mid-auth; version-mismatched mock clients in CI; security scanners sending malformed sequences.","solutions":["Use a real libpq client (psql, tokio-postgres) which sends the password message when AuthenticationCleartextPassword is received","Verify you are connecting to the intended endpoint and auth mode (trust vs NeonJWT) for this listener","If writing a custom client, follow the flow: Startup -> AuthenticationCleartextPassword -> PasswordMessage(jwt)","Capture the traffic and check what message replaced the password response (the {:?} in the log names it)"],"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(\"while waiting for handshake\") =>\n    {\n        // Wrong message order from the client; connection is unusable.\n        // The {:?} names the offending message -- log it, close, do not retry on this socket.\n        tracing::warn!(peer = ?peer_addr, error = %e, \"handshake protocol violation\");\n        // server already sent / should send an ErrorResponse before closing\n    }\n    other => other?,\n}","preventionTips":["Use battle-tested clients (libpq, tokio-postgres) which follow AuthenticationCleartextPassword with exactly one PasswordMessage","In custom drivers, encode the auth state machine explicitly and forbid Query before AuthenticationOk","Have proxies pass messages through unmodified during handshake -- never re-inject startup packets","Include an auth-flow integration test (startup -> password -> query) for every new client wrapper"],"tags":["rust","postgres-protocol","handshake","authentication","neon-jwt"],"backgroundTag":"postgres-protocol-handshake-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}