cube-js/cube · error

Unexpected x-clickhouse-format in response: expected ${forma

Error message

Unexpected x-clickhouse-format in response: expected ${format}, received ${resultSet.response_headers['x-clickhouse-format']}

What it means

In ClickHouseDriver.queryResponse, after executing a query the driver checks the optional `x-clickhouse-format` response header. The comment states it's optional, but if present it must match the requested format (usually JSON). A mismatch means ClickHouse (or an intermediary) returned data in a different format than the client will try to parse, so the driver throws to prevent corrupt result parsing.

Source

Thrown at packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts:294

  protected queryResponse(query: string, values: unknown[]): Promise<ResponseJSON<Record<string, unknown>>> {
    const formattedQuery = sqlstring.format(query, values);

    return this.withCancel(async (connection, queryId, signal) => {
      try {
        const format = 'JSON';

        const resultSet = await connection.query({
          query: formattedQuery,
          query_id: queryId,
          format,
          clickhouse_settings: this.config.clickhouseSettings,
          abort_signal: signal,
        });

        // response_headers['x-clickhouse-format'] is optional, but if it exists,
        // it should match the requested format.
        if (resultSet.response_headers['x-clickhouse-format'] && resultSet.response_headers['x-clickhouse-format'] !== format) {
          throw new Error(`Unexpected x-clickhouse-format in response: expected ${format}, received ${resultSet.response_headers['x-clickhouse-format']}`);
        }

        // We used format JSON, so we expect each row to be Record with column names as keys
        const results = await resultSet.json<Record<string, unknown>>();
        return results;
      } catch (e) {
        // TODO replace string formatting with proper cause
        throw new Error(`Query failed: ${e}; query id: ${queryId}`);
      }
    });
  }

  protected normaliseResponse<R = unknown>(res: ResponseJSON<Record<string, unknown>>): Array<R> {
    if (res.data) {
      const meta = (res.meta ?? []).reduce<Record<string, { name: string; type: string; }>>(
        (state, element) => ({ [element.name]: element, ...state }),
        {}
      );

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Compare expected vs received format in the message and find what rewrote the response (proxy, settings, custom query)
  2. Remove/fix clickhouse_settings that override the output format (e.g. output_format_* settings)
  3. Check any intermediary (ChProxy, ingress) that rewrites ClickHouse responses or their headers
  4. Upgrade @clickhouse/client and driver to aligned versions if a server/client format negotiation changed

Example fix

// before: settings force another format
query(`SELECT ...`, { settings: { output_format_json_quote_64bit_integers: 1, ... } })
// after: keep default JSON format expected by the driver
query(`SELECT ...`)
Defensive patterns

Strategy: validation

Try / catch

try {
  const rows = await cube.query(query);
} catch (e) {
  if (e.message.includes('Unexpected x-clickhouse-format')) {
    // find proxy/settings rewriting the response format
  }
  throw e;
}

Prevention

When it happens

Trigger: A query sent via query() whose response carries an x-clickhouse-format header differing from the requested `format` — e.g. a proxy/gateway rewriting the format, ClickHouse returning an error body with a different format header, or custom clickhouse_settings forcing another output format.

Common situations: ClickHouse behind an API gateway/ChProxy altering response headers; server-side FORMAT clause injected via settings; version mismatches where the client expects JSON but the server emits JSONCompact; error responses that still include a format header.

Related errors


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