cube-js/cube · error

Unexpected stream end before row with names

Error message

Unexpected stream end before row with names

What it means

While streaming JSONCompactEachRowWithNamesAndRows data, ClickHouseDriver.stream expects the first row of the stream to be column names and the second to be types. If the async iterator ends before producing the first row (empty body, truncated connection, or error swallowed as empty output), the driver throws 'Unexpected stream end before row with names' because the schema header required to decode subsequent rows is absent.

Source

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

        throw new Error(`Unexpected x-clickhouse-format in response: expected ${format}, received ${resultSet.response_headers['x-clickhouse-format']}`);
      }

      // Array<unknown> is okay, because we use fixed JSONCompactEachRowWithNamesAndTypes format
      // And each row after first two will look like this: [42, "hello", [0,1]]
      // https://clickhouse.com/docs/en/interfaces/formats#jsoncompacteachrowwithnamesandtypes
      const resultSetStream = resultSet.stream<Array<unknown>>();

      const allRowsIter = (async function* allRowsIter() {
        for await (const rowsBatch of resultSetStream) {
          for (const row of rowsBatch) {
            yield row.json();
          }
        }
      }());

      const first = await allRowsIter.next();
      if (first.done) {
        throw new Error('Unexpected stream end before row with names');
      }
      // JSONCompactEachRowWithNamesAndTypes: expect first row to be column names as string
      const names = first.value as Array<string>;

      const second = await allRowsIter.next();
      if (second.done) {
        throw new Error('Unexpected stream end before row with types');
      }
      // JSONCompactEachRowWithNamesAndTypes: expect first row to be column names as string
      const types = second.value as Array<string>;

      if (names.length !== types.length) {
        throw new Error(`Unexpected names and types length mismatch; names ${names.length} vs types ${types.length}`);
      }

      const dataRowsIter = (async function* () {
        for await (const row of allRowsIter) {
          yield transformStreamRow(row, names, types);

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Retry the download — this is often a transient connection drop during streaming
  2. Check ClickHouse server logs around the time of the query for mid-query exceptions
  3. Increase proxy/load-balancer idle timeouts if large exports take long between rows
  4. Reduce result size (partition the export) so the stream completes within network/proxy limits

Example fix

// before: huge export dropped by proxy idle timeout
await downloadQueryResults(query) // 10GB result
// after: limit rows per export
await downloadQueryResults(queryWithTimeWindowAndLimit)
Defensive patterns

Strategy: retry

Try / catch

try {
  await downloadQueryResults(query);
} catch (e) {
  if (e.message === 'Unexpected stream end before row with names') {
    // transient stream drop: retry with backoff, or chunk the query
  }
  throw e;
}

Prevention

When it happens

Trigger: downloadQueryResults/stream when the ClickHouse response stream closes before emitting any row: empty result body, connection reset mid-response, server error returned as empty stream, or query producing no header row.

Common situations: Network interruption between Cube and ClickHouse during large downloads; server crash/timeout during export; proxy buffering timeouts closing idle streams; queries that error after headers were expected but produce empty output.

Related errors


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