cube-js/cube · error

Unexpected stream end before row with types

Error message

Unexpected stream end before row with types

What it means

The ClickHouse driver uses the JSONCompactEachRowWithNamesAndTypes format, where the response stream must begin with two rows: one containing column names and the next containing column types. This error is thrown when the stream ends before the second (types) row arrives, meaning ClickHouse returned fewer rows than the format guarantees.

Source

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

      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);
        }
      }());
      const rowStream = Readable.from(dataRowsIter);

      return {
        rowStream,
        types: names.map((name, idx) => {

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Check network stability between the driver and ClickHouse (proxies, LB idle/read timeouts) and raise streaming read timeouts
  2. Retry the query; transient truncation is often resolved on retry
  3. Verify the ClickHouse server version supports JSONCompactEachRowWithNamesAndTypes correctly
  4. Capture ClickHouse server logs for the query id at the time of failure to find server-side errors
  5. Upgrade the cubejs-clickhouse-driver package to pick up stream-robustness fixes

Example fix

// before
try {
  await dataSourceStorage.downloadQueryResults(query, values);
} catch (e) { /* unhandled: Unexpected stream end before row with types */ }
// after
try {
  return await driver.downloadQueryResults(query, values);
} catch (e) {
  if (String(e.message).includes('Unexpected stream end')) {
    return driver.downloadQueryResults(query, values); // retry transient truncation
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

try { await driver.downloadQueryResults(q, v); } catch (e) { if (/Unexpected stream end/.test(String(e))) { /* retry */ } else throw e; }

Prevention

When it happens

Trigger: Calling downloadQueryResults() which invokes stream(); the allRowsIter yields the first row (names) but next() returns done before a second row — e.g. ClickHouse returns an empty or malformed result body, or the connection is cut off after the first row.

Common situations: Network interruptions between driver and ClickHouse; ClickHouse server errors that truncate the response; proxies/load balancers with aggressive timeouts cutting streaming responses; ClickHouse returning a partial body on large result sets; mismatched ClickHouse versions or format support.

Related errors


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