{"record":{"id":"0b6b4907050c68a2","repo":"beekeeper-studio/beekeeper-studio","slug":"query-not-ready-to-be-canceled-0b6b49","errorCode":null,"errorMessage":"Query not ready to be canceled","messagePattern":"Query not ready to be canceled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/studio/src/lib/db/clients/sqlite.ts","lineNumber":315,"sourceCode":"          if (err.code === sqliteErrors.CANCELED) {\n            err.sqlectronError = 'CANCELED_BY_USER';\n          }\n\n          if (err.message?.startsWith('no such column')) {\n            const nuError = new ClientError(`${err.message} - Check that you only use double quotes (\") for identifiers, not strings`, \"https://docs.beekeeperstudio.io/support/troubleshooting/#no-such-column-x\");\n            throw nuError\n          }\n\n          throw err;\n        } finally {\n          if (queryConnection !== this._rawConnection) {\n            queryConnection.close();\n          }\n        }\n      }).bind(this),\n      async cancel() {\n        if (!queryConnection) {\n          throw new Error('Query not ready to be canceled');\n        }\n      }\n    }\n  }\n\n  async executeQuery(queryText: string, options: any = {}): Promise<NgQueryResult[]> {\n    const arrayMode: boolean = options.arrayMode;\n    const result = await this.driverExecuteMultiple(queryText, options);\n    const commands = this.identifyCommands(queryText)\n\n    return (result || []).map(({ rows: data, columns, statement, changes }, i) => {\n      // Fallback in case the identifier could not reconize the command\n      const text = commands[i]?.text;\n      const isSelect = Array.isArray(data);\n      let rows: any[];\n      let fields: any[];\n\n      if (isSelect && arrayMode) {","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/beekeeper-studio/beekeeper-studio/blob/4e3e03e3223b2b71a1af8926d455f533b80af837/apps/studio/src/lib/db/clients/sqlite.ts#L297-L333","documentation":"In SqliteClient's query execution, a dedicated worker 'queryConnection' is opened asynchronously while the cancel() closure is returned immediately. cancel() throws 'Query not ready to be canceled' if the closure-scoped queryConnection variable is still null — i.e. cancellation was requested before the worker connection finished opening (or it was never assigned). It signals the query cannot be canceled at that moment, not that the query itself failed.","triggerScenarios":"Calling cancel() on the query handle immediately after starting a query, before the SQLite worker connection has opened; canceling after the connection already closed; racing a cancel against a very fast query that finished and tore down the connection.","commonSituations":"UIs with a cancel button that can be clicked during the brief connection-setup window; automated tests canceling instantly; user cancels a long query twice and the second cancel lands after teardown.","solutions":["Delay cancellation slightly and retry until queryConnection is assigned (poll or wait a tick).","Treat this error as benign: check whether the query already completed before showing a failure.","Serialize cancellation: disable the cancel button until the query reports it is actually running.","Track the query's lifecycle and make cancel() a no-op when the query is already finished or the connection closed."],"exampleFix":"// before\nawait handle.cancel();\n// after\ntry {\n  await handle.cancel();\n} catch (err) {\n  if (!/not ready to be canceled/i.test(err.message)) throw err; // query likely already done\n}","handlingStrategy":"retry","validationCode":"// only cancel once the query reports it is running\nif (!queryHandle.isRunning || !queryHandle.isRunning()) return;","typeGuard":"function isCancelable(handle: QueryHandle): boolean {\n  return typeof handle.cancel === 'function' && handle.isRunning?.() === true;\n}","tryCatchPattern":"const cancelWithRetry = async (handle, retries = 3) => {\n  for (let i = 0; i < retries; i++) {\n    try { return await handle.cancel(); }\n    catch (err) {\n      if (!/not ready to be canceled/i.test(err.message) || i === retries - 1) {\n        if (/not ready to be canceled/i.test(err.message)) return; // query likely done\n        throw err;\n      }\n      await new Promise(r => setTimeout(r, 100));\n    }\n  }\n};","preventionTips":["Delay enabling the cancel button until the query reports running","Treat 'not ready to be canceled' as benign (query setup or already finished)","Debounce duplicate cancel clicks","Remember SQLite queries run on a worker connection opened asynchronously"],"tags":["sqlite","cancel","race-condition","lifecycle"],"backgroundTag":"query-cancel-not-ready","analyzedSha":"4e3e03e3223b2b71a1af8926d455f533b80af837","analyzedAt":"2026-08-31T23:21:23.379Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}