hasura/graphql-engine · error · ParseError
Unable to parse WebSocket message: {0}
Error message
Unable to parse WebSocket message: {0} What it means
This variant of ParseError wraps a serde_json::Error raised when a received WebSocket text message cannot be deserialized into the graphql-ws ClientMessage type. It means the frame was valid WebSocket but not a valid graphql-ws protocol message (bad JSON or wrong shape). The server cannot interpret the message and reports this error.
Source
Thrown at v3/crates/graphql/graphql-ws/src/websocket/tasks.rs:166
)
.await
.into_inner();
if break_loop == BreakLoop::Break {
break;
}
}
}
enum ParsedClientMessage {
Close,
Protocol(protocol::types::ClientMessage),
}
#[derive(thiserror::Error, Debug)]
enum ParseError {
#[error("Unable to fetch message from WebSocket: {0}")]
WebSocket(#[from] axum::Error),
#[error("Unable to parse WebSocket message: {0}")]
Json(#[from] serde_json::Error),
}
impl tracing_util::TraceableError for ParseError {
fn visibility(&self) -> tracing_util::ErrorVisibility {
tracing_util::ErrorVisibility::User
}
}
fn parse_incoming_message(
message: Result<ws::Message, axum::Error>,
) -> Result<ParsedClientMessage, ParseError> {
let tracer = tracing_util::global_tracer();
tracer.in_span(
"parse_incoming_message",
"Parse WebSocket message frame",
tracing_util::SpanVisibility::User,
|| {View on GitHub (pinned to 724551b9ae)
Solutions
- Validate outgoing frames against the graphql-ws message spec (type must be one of connection_initsubscribe... with required fields per message)
- Use a maintained client library (graphql-ws npm package) matching the server protocol version instead of hand-writing messages
- Capture the offending frame and compare its shape with the protocol's ClientMessage definitions
Example fix
// before (client sends invalid frame)
ws.send(JSON.stringify({ id: '1', type: 'start', payload: { query } }));
// after
ws.send(JSON.stringify({ id: '1', type: 'subscribe', payload: { query } })); Defensive patterns
Strategy: validation
Validate before calling
const VALID_TYPES = new Set(['connection_init','ping','pong','subscribe','complete']);
function isValidClientMessage(msg: unknown): boolean {{
const m = msg as {{ type?: string }};
return typeof m?.type === 'string' && VALID_TYPES.has(m.type);
}}
if (!isValidClientMessage(parsed)) ws.send(JSON.stringify({{ type: 'ping' }})); Type guard
function isClientMessage(v: unknown): v is {{ type: string; [k: string]: unknown }} {{
return typeof v === 'object' && v !== null && typeof (v as any).type === 'string';
}} Prevention
- Validate frames against the graphql-ws message spec before sending
- Use a protocol-conformant client library instead of hand-rolled sends
- Pin client and server to compatible graphql-ws protocol versions
When it happens
Trigger: A client sends a JSON message that doesn't match any ClientMessage variant of the graphql-ws protocol — e.g. unknown message 'type', missing required fields, or a protocol-version mismatch producing different message shapes.
Common situations: Hand-rolled WebSocket clients sending arbitrary JSON; clients implementing a different GraphQL subscription protocol (subscriptions-transport-ws) against a graphql-ws server; version skew between client and server protocol implementations.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Expecting {} protocol
- subscription are not supported over HTTP
- Connection already initialized
- Invalid header name: {0}
- Connection initialization timed out
AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28).
Data as JSON: /api/errors/a46d09ffdb8c0b02.
Report an issue: GitHub.