cube-js/cube · error · ConnectionError
Cube Store connection is closed
Error message
Cube Store connection is closed
What it means
WebSocketConnection.initWebSocket refuses to open a new WebSocket after the connection has been closed via close(), throwing a ConnectionError. This makes close terminal: any query issued after release is rejected instead of silently re-opening a socket. In-flight messages on the re-send path are also rejected with this error.
Source
Thrown at packages/cubejs-cubestore-driver/src/WebSocketConnection.ts:120
// messages that were still in flight at that point.
private closedAt: Date | null = null;
public constructor(url: string) {
this.url = url;
this.messageCounter = 1;
this.maxConnectRetries = getEnv('cubeStoreMaxConnectRetries');
this.noHeartBeatTimeout = getEnv('cubeStoreNoHeartBeatTimeout');
this.maxMessageSize = getEnv('cubeStoreMaxMessageSize');
this.currentConnectionTry = 0;
this.connectionId = uuidv4();
}
protected async initWebSocket(): Promise<CubeStoreWebSocket> {
if (this.closed) {
// Refusing here is what makes close() terminal: it covers a query issued
// after the release as well as the re-send path, whose `catch` then
// rejects the messages that were in flight instead of re-opening for them.
throw new ConnectionError('Cube Store connection is closed');
}
if (!this.webSocket) {
const headers: Record<string, string> = {};
headers['x-process-id'] = getProcessUid();
const webSocket = new WebSocket(this.url, { headers, maxPayload: this.maxMessageSize }) as CubeStoreWebSocket;
webSocket.on('upgrade', (response: any) => {
this.cubeStoreVersion = response.headers['x-cubestore-version'] || null;
});
webSocket.readyPromise = new Promise<CubeStoreWebSocket>((resolve, reject) => {
webSocket.lastHeartBeat = new Date();
const pingInterval = setInterval(() => {
if (webSocket.readyState === WebSocket.OPEN) {
webSocket.ping();
}
View on GitHub (pinned to 7d981676b3)
Solutions
- Ensure queries complete before calling close()/release()
- Re-create the driver/connection instead of reusing a closed one
- Add readiness checks before dispatching queries during shutdown
- In tests, scope driver lifetime to the test and close last
Example fix
// before
connection.close();
await connection.query('SELECT 1', []); // throws ConnectionError
// after
await connection.query('SELECT 1', []);
connection.close(); Defensive patterns
Strategy: try-catch
Validate before calling
// track lifecycle in a wrapper
let released = false;
const guard = (conn) => { if (released) throw new Error('driver already released'); return conn; }; Type guard
const isConnectionClosed = (e) => e instanceof ConnectionError && /connection is closed/i.test(e.message);
Try / catch
try {
await driver.query(sql, params);
} catch (e) {
if (isConnectionClosed(e)) {
driver = createDriver(); // recreate instead of reusing closed connection
return driver.query(sql, params);
}
throw e;
} Prevention
- Close/release connections only after all queries settle
- Track connection lifecycle explicitly (e.g. a released flag)
- Scope driver lifetime per request or per app, not shared after teardown
- In tests, always close drivers in afterAll, not between assertions
When it happens
Trigger: Calling driver.query()/release() or re-sending a queued message after WebSocketConnection.close() has been called; using a driver after its connection was released (e.g. after pool teardown or process shutdown).
Common situations: App shutting down while queries are still queued; a query orchestrator retrying a request after the Cube Store connection was released; holding a driver reference past its lifecycle in tests.
Related errors
- Cube Store missed message id: ${httpMessage.messageId()}
- Cube Store request size of ${formatSize(buffer.length)} exce
- Cube Store response size exceeds the maximum message size of
- Request of {} bytes exceeds the maximum message size of {} b
- connect timeout
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/bdad47fdd32b60e0.
Report an issue: GitHub.