{"record":{"id":"c599bb2eb07e23b6","repo":"tobi/qmd","slug":"session-aborted","errorCode":null,"errorMessage":"Session aborted","messagePattern":"Session aborted","errorType":"exception","errorClass":"SessionReleasedError","httpStatus":null,"severity":"error","filePath":"src/llm.ts","lineNumber":2033,"sourceCode":"    }\n\n    this.abortController.abort(new Error(\"Session released\"));\n    this.manager.release();\n  }\n\n  /**\n   * Wrap an operation with tracking and abort checking.\n   */\n  private async withOperation<T>(fn: () => Promise<T>): Promise<T> {\n    if (!this.isValid) {\n      throw new SessionReleasedError();\n    }\n\n    this.manager.operationStart();\n    try {\n      // Check abort before starting\n      if (this.abortController.signal.aborted) {\n        throw new SessionReleasedError(\n          this.abortController.signal.reason?.message || \"Session aborted\"\n        );\n      }\n      return await fn();\n    } finally {\n      this.manager.operationEnd();\n    }\n  }\n\n  async embed(text: string, options?: EmbedOptions): Promise<EmbeddingResult | null> {\n    return this.withOperation(() => this.manager.getLlamaCpp().embed(text, options));\n  }\n\n  async embedBatch(texts: string[], options?: EmbedOptions): Promise<(EmbeddingResult | null)[]> {\n    return this.withOperation(() => this.manager.getLlamaCpp().embedBatch(texts, options));\n  }\n\n  async expandQuery(","sourceCodeStart":2015,"sourceCodeEnd":2051,"githubUrl":"https://github.com/tobi/qmd/blob/dbfd0b4736aeaf761d1a16ca8e424f071df8feb9/src/llm.ts#L2015-L2051","documentation":"A queued session operation began executing after AbortController.abort() was already called. The wrapper re-checks the abort signal before invoking fn() and surfaces the abort reason (defaulting to 'Session aborted') as a SessionReleasedError.","triggerScenarios":"Calling a session operation (embed, generate, rerank) after session.close()/dispose() or explicit abort(); or a queued operation whose signal was aborted while it waited in the queue.","commonSituations":"Timeout logic that aborts the session while requests are still queued; concurrent callers racing with shutdown; idle-unload of models aborting in-flight queued work.","solutions":["Check session liveness (not aborted/disposed) before submitting work and re-create the session if aborted","Await or cancel pending operations before calling abort()/close()","Propagate the abort reason and retry the operation on a fresh session"],"exampleFix":"// before\nconst r = await session.run(fn); // may already be aborted\n// after\nif (session.abortController.signal.aborted) await recreateSession();\nconst r = await session.run(fn);","handlingStrategy":"retry","validationCode":"if (session.abortController.signal.aborted) await recreateSession();","typeGuard":"const isSessionUsable = (s: Session) => !s.abortController.signal.aborted;","tryCatchPattern":"try { return await session.run(fn); } catch (e) { if (e instanceof SessionReleasedError) { await recreateSession(); return session.run(fn); } throw e; }","preventionTips":["Drain or cancel queued ops before abort/close","Check signal.aborted before enqueueing","Add timeouts so aborts don't race queued work"],"tags":["abort","session","race-condition","lifecycle"],"backgroundTag":"aborted-operation","analyzedSha":"dbfd0b4736aeaf761d1a16ca8e424f071df8feb9","analyzedAt":"2026-08-28T18:07:46.628Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}