{"record":{"id":"8b83462787aba00b","repo":"mastra-ai/mastra","slug":"knowledge-semantic-index-is-stale-a-visible-opera","errorCode":null,"errorMessage":"Knowledge semantic index is stale: a visible operation is pending or being processed by another worker.","messagePattern":"Knowledge semantic index is stale: a visible operation is pending or being processed by another worker\\.","errorType":"exception","errorClass":"StaleKnowledgeSemanticIndexError","httpStatus":null,"severity":"warning","filePath":"packages/memory/src/processors/observational-memory/subconscious/semantic-index.ts","lineNumber":126,"sourceCode":"      .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id))\n      .slice(0, limit);\n  }\n\n  async #drain(scope?: KnowledgeScope): Promise<number> {\n    let processed = 0;\n    for (let batch = 0; batch < MAX_DRAIN_BATCHES; batch++) {\n      const entries = await this.#knowledge.claimSemanticOutbox({\n        workerId: this.#workerId,\n        limit: this.#batchSize,\n        scope,\n      });\n      if (entries.length === 0) {\n        const [pending, processing] = await Promise.all([\n          this.#knowledge.listSemanticOutbox({ status: 'pending', scope, limit: 1 }),\n          this.#knowledge.listSemanticOutbox({ status: 'processing', scope, limit: 1 }),\n        ]);\n        if (pending.length > 0 || processing.length > 0) {\n          throw new StaleKnowledgeSemanticIndexError(\n            'Knowledge semantic index is stale: a visible operation is pending or being processed by another worker.',\n          );\n        }\n        return processed;\n      }\n\n      for (let index = 0; index < entries.length; index++) {\n        const entry = entries[index]!;\n        try {\n          await this.#apply(entry);\n          await this.#knowledge.completeSemanticOutbox({ ids: [entry.id], workerId: this.#workerId });\n          processed++;\n        } catch (error) {\n          await this.#knowledge.releaseSemanticOutbox({\n            ids: entries.slice(index).map(pendingEntry => pendingEntry.id),\n            workerId: this.#workerId,\n          });\n          throw new StaleKnowledgeSemanticIndexError(","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/subconscious/semantic-index.ts#L108-L144","documentation":"`#drain` (semantic-index.ts:126) processes the semantic outbox so the index is current before searching. When its own batch is empty but the store still reports pending or processing outbox operations for the visible scope, another worker owns those writes; the index may not yet reflect recent knowledge, so it throws `StaleKnowledgeSemanticIndexError` rather than serving possibly-stale results.","triggerScenarios":"Concurrent workers: this instance drains zero entries while `listSemanticOutbox({ status: 'pending' | 'processing', scope })` returns at least one entry — another worker is mid-write, or a crashed worker left operations stuck in 'processing' without releasing them.","commonSituations":"Multiple app instances writing knowledge simultaneously; a worker crashed mid-batch leaving operations locked in 'processing'; long-running batch jobs holding the outbox while a request triggers remind.","solutions":["Retry the search after a short delay so the other worker finishes draining (the error is designed to be transient).","For stuck 'processing' entries, ensure worker leases expire or call releaseSemanticOutbox for the dead worker's IDs.","Reduce concurrency on the same scope, or route remind traffic to a single indexer per scope."],"exampleFix":"// before\nawait remind(context); // throws while another worker drains\n// after\ntry {\n  await remind(context);\n} catch (e) {\n  if (e instanceof StaleKnowledgeSemanticIndexError) {\n    await delay(1000);\n    return remind(context); // retry\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"const [pending, processing] = await Promise.all([\n  store.listSemanticOutbox({ status: 'pending', scope, limit: 1 }),\n  store.listSemanticOutbox({ status: 'processing', scope, limit: 1 }),\n]);\nconst busy = pending.length > 0 || processing.length > 0;","typeGuard":"function isOutboxIdle(entries) {\n  return entries.length === 0;\n}","tryCatchPattern":"try {\n  return await remind(context);\n} catch (e) {\n  if (e instanceof StaleKnowledgeSemanticIndexError && e.message.includes('pending or being processed')) {\n    await new Promise((r) => setTimeout(r, 1000));\n    return remind(context); // transient: another worker is draining\n  }\n  throw e;\n}","preventionTips":["Wrap remind/search in bounded retry with backoff.","Monitor for outbox entries stuck in 'processing' and release leases of dead workers.","Limit concurrent writers per scope or use a single indexer worker."],"tags":["concurrency","outbox","stale-index"],"backgroundTag":"stale-semantic-index","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}