cube-js/cube · error · CubejsHandlerError

Invalid unsubscribe message format

Error message

Invalid unsubscribe message format

What it means

A WebSocket message containing an 'unsubscribe' property is validated against unsubscribeMessageSchema (expecting the correct key/id per schema). Zod validation failure throws CubejsHandlerError(400, 'Invalid unsubscribe message format') with a field-level detail.

Source

Thrown at packages/cubejs-api-gateway/src/ws/subscription-server.ts:78

    return error.issues
      .map(e => (e.path.length ? `${e.path.join('.')}: ${e.message}` : e.message))
      .join(', ');
  }

  protected validateMessage(message: object): WsMessage {
    if ('authorization' in message) {
      const result = authMessageSchema.safeParse(message);
      if (!result.success) {
        throw new CubejsHandlerError(400, 'Invalid authorization message format', this.mapZodError(result.error));
      }

      return result.data;
    }

    if ('unsubscribe' in message) {
      const result = unsubscribeMessageSchema.safeParse(message);
      if (!result.success) {
        throw new CubejsHandlerError(400, 'Invalid unsubscribe message format', this.mapZodError(result.error));
      }

      return result.data;
    }

    const result = methodMessageSchema.safeParse(message);
    if (!result.success) {
      throw new CubejsHandlerError(400, 'Invalid message format', this.mapZodError(result.error));
    }

    return result.data;
  }

  public async processMessage(connectionId: string, body: string) {
    let message: any | undefined;

    try {
      message = this.deserializeMessage(body);

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Send the exact shape unsubscribeMessageSchema requires (typically { messageId, unsubscribe: '<subscriptionId>' } with a string id).
  2. Use the error's Zod detail (field path and message) to correct the offending property.
  3. Switch to the official WebSocketTransport/subscribe() API so unsubscribe frames are built for you.

Example fix

// before
socket.send(JSON.stringify({ unsubscribe: 42 }));
// after
socket.send(JSON.stringify({ messageId: '3', unsubscribe: '42' }));
Defensive patterns

Strategy: validation

Validate before calling

function isValidUnsubscribeMessage(msg: unknown): boolean {
  const m = msg as any;
  return typeof m === 'object' && m !== null &&
    typeof m.messageId === 'string' &&
    typeof m.unsubscribe === 'string';
}

Type guard

function isUnsubscribeMessage(m: unknown): m is { messageId: string, unsubscribe: string } {
  return typeof m === 'object' && m !== null && 'unsubscribe' in m &&
    typeof (m as any).unsubscribe === 'string';
}

Try / catch

try {
  await handleMessage(frame);
} catch (e) {
  if (e.status === 400 && e.error === 'Invalid unsubscribe message format') {
    console.error('Unsubscribe frame rejected:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Sending { unsubscribe: ... } with a missing, misnamed, or wrong-typed value — e.g. { unsubscribe: 123 } when a string subscription id is required, or embedding extra required-incompatible fields.

Common situations: Hand-rolled clients guessing the unsubscribe protocol; storing numeric subscription ids from a different API and sending them; client/server version mismatch after an upgrade of the WS protocol schema.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/8caca555a0ffcaae. Report an issue: GitHub.