cube-js/cube · error

Unexpected x-clickhouse-format in response: expected

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.

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.

Appendix: 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)