{"record":{"id":"29310b2c13e2e7ea","repo":"tobi/qmd","slug":"llm-operations-are-disabled-in-ci-set-ci-true","errorCode":null,"errorMessage":"LLM operations are disabled in CI (set CI=true)","messagePattern":"LLM operations are disabled in CI \\(set CI=true\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/llm.ts","lineNumber":1475,"sourceCode":"\n      const embedding = await context.getEmbeddingFor(safeText);\n\n      return {\n        embedding: Array.from(embedding.vector),\n        model: options.model ?? this.embedModelUri,\n      };\n    } catch (error) {\n      console.error(\"Embedding error:\", error);\n      return null;\n    }\n  }\n\n  /**\n   * Batch embed multiple texts efficiently\n   * Uses Promise.all for parallel embedding - node-llama-cpp handles batching internally\n   */\n  async embedBatch(texts: string[], options: EmbedOptions = {}): Promise<(EmbeddingResult | null)[]> {\n    if (this._ciMode) throw new Error(\"LLM operations are disabled in CI (set CI=true)\");\n    // Ping activity at start to keep models alive during this operation\n    this.touchActivity();\n\n    if (texts.length === 0) return [];\n\n    try {\n      const contexts = await this.ensureEmbedContexts();\n      const n = contexts.length;\n\n      if (n === 1) {\n        // Single context: sequential (no point splitting)\n        const context = contexts[0]!;\n        const embeddings: ({ embedding: number[]; model: string } | null)[] = [];\n        for (const text of texts) {\n          try {\n            const { text: safeText, truncated, limit } = await this.truncateToContextSize(text);\n            if (truncated) {\n              console.warn(`⚠ Batch text truncated to fit embedding context (${limit} tokens)`);","sourceCodeStart":1457,"sourceCodeEnd":1493,"githubUrl":"https://github.com/tobi/qmd/blob/dbfd0b4736aeaf761d1a16ca8e424f071df8feb9/src/llm.ts#L1457-L1493","documentation":"embedBatch() refuses to run when the LlamaCpp instance is in CI mode (_ciMode). CI mode disables all local LLM operations so that test/CI environments never attempt to load models or run inference.","triggerScenarios":"Calling embedBatch(texts) while the process was constructed with CI mode enabled — e.g. NODE_ENV=test, CI=true env detection at construction time, or a test helper that instantiates LlamaCpp in CI mode.","commonSituations":"Running unit tests that accidentally call the real embedBatch; CI pipelines where model downloads/inference must be skipped; forgetting to unset CI mode in a local integration test that needs real embeddings.","solutions":["Mock embedBatch in tests instead of calling the real one","Set the env so CI mode is off (e.g. unset CI/NODE_ENV=test) if you genuinely need embeddings locally","Use FTS-only search paths (qmd search) in CI"],"exampleFix":"// before\nconst embs = await llm.embedBatch(texts); // throws in CI\n// after\nconst embs = llm.ciMode ? texts.map(() => null) : await llm.embedBatch(texts);","handlingStrategy":"try-catch","validationCode":"if (llm.ciMode) return texts.map(() => null);","typeGuard":"const canEmbed = (l: LlamaCpp) => !l.ciMode;","tryCatchPattern":"try { await llm.embedBatch(texts); } catch (e) { if (/disabled in CI/.test((e as Error).message)) return texts.map(() => null); throw e; }","preventionTips":["Mock LLM methods in unit tests","Centralize a ciMode check helper before any LLM call","Never assume LLM availability in test environments"],"tags":["ci","embedding","test-mode","llm"],"backgroundTag":"feature-disabled-in-ci","analyzedSha":"dbfd0b4736aeaf761d1a16ca8e424f071df8feb9","analyzedAt":"2026-08-28T18:07:46.628Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}