{"record":{"id":"cce78c57212e810f","repo":"mastra-ai/mastra","slug":"observational-memory-thread-id-required","errorCode":"OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED","errorMessage":"OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED: ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList. Ensure the agent is configured with Memory and a valid threadId is provided.","messagePattern":"OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED: ObservationalMemory \\(scope: 'thread'\\) requires a threadId, but none was found in RequestContext or MessageList\\. Ensure the agent is configured with Memory and a valid threadId is provided\\.","errorType":"error_code","errorClass":"MastraError","httpStatus":400,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/observational-memory.ts","lineNumber":1820,"sourceCode":"        threadId: memoryContext.thread.id,\n        resourceId: memoryContext.resourceId,\n      };\n    }\n\n    // Fallback to MessageList's memoryInfo\n    const serialized = messageList.serialize();\n    if (serialized.memoryInfo?.threadId) {\n      return {\n        threadId: serialized.memoryInfo.threadId,\n        resourceId: serialized.memoryInfo.resourceId,\n      };\n    }\n\n    // In thread scope, threadId is required — without it OM would silently\n    // fall back to a resource-keyed record which causes deadlocks when\n    // multiple threads share the same resourceId.\n    if (this.scope === 'thread') {\n      throw new MastraError({\n        id: 'OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED',\n        domain: ErrorDomain.MASTRA_MEMORY,\n        category: ErrorCategory.USER,\n        details: { status: 400 },\n        text:\n          `ObservationalMemory (scope: 'thread') requires a threadId, but none was found in RequestContext or MessageList. ` +\n          `Ensure the agent is configured with Memory and a valid threadId is provided.`,\n      });\n    }\n\n    return null;\n  }\n\n  /**\n   * Save messages to storage, skipping messages that were already persisted by\n   * async buffering. Uses the message-level sealed flag (metadata.mastra.sealed)\n   * to detect already-persisted messages, avoiding redundant DB operations.\n   *","sourceCodeStart":1802,"sourceCodeEnd":1838,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/observational-memory.ts#L1802-L1838","documentation":"In `scope: 'thread'` mode, ObservationalMemory keys all its state per thread. Without a threadId it would silently fall back to a resource-keyed record, which deadlocks when multiple threads share one resourceId. The processor therefore throws a MastraError (OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED, USER category) when it cannot find a threadId in either the RequestContext or the MessageList.","triggerScenarios":"Calling `agent.generate`/`agent.stream` (or memory retrieval paths) against an agent whose memory uses ObservationalMemory with `scope: 'thread'`, while passing no threadId in RequestContext and no thread-scoped message in the MessageList (observational-memory.ts:1820).","commonSituations":"Fire-and-forget scripts or cron jobs that call the agent without creating/passing a thread; API routes that drop the threadId header; tests that construct RequestContext without thread metadata; migrating from resource-scoped OM to thread-scoped without updating callers.","solutions":["Create/reuse a thread and pass its threadId (e.g. via RequestContext or by including thread-scoped messages in the MessageList).","If per-thread state is not needed, switch ObservationalMemory to `scope: 'resource'`.","Ensure the agent is configured with a working Memory instance so threadId resolution has a source.","In API layers, require and forward the threadId on every request before invoking the agent."],"exampleFix":"// before\nawait agent.generate('hello'); // no thread context\n// after\nawait agent.generate('hello', {\n  memory: { thread: threadId, resource: resourceId },\n});","handlingStrategy":"try-catch","validationCode":"const threadId = requestContext.get?.('threadId') ?? messages.find(m => m.threadId)?.threadId;\nif (memory?.observational?.scope === 'thread' && !threadId) {\n  throw new Error('scope: thread requires a threadId');\n}","typeGuard":"function hasThreadId(ctx: { threadId?: string }): ctx is { threadId: string } {\n  return typeof ctx.threadId === 'string' && ctx.threadId.length > 0;\n}","tryCatchPattern":"try {\n  await agent.generate(input, options);\n} catch (e) {\n  if (e instanceof MastraError && e.id === 'OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED') {\n    // create a thread and retry with explicit memory context\n    const thread = await memory.createThread({ resourceId });\n    return agent.generate(input, { ...options, memory: { thread: thread.id, resource: resourceId } });\n  }\n  throw e;\n}","preventionTips":["Always pass explicit memory context (thread + resource) on agent calls when using thread-scoped OM.","Create the thread up front in request handlers before invoking the agent.","Catch OBSERVATIONAL_MEMORY_THREAD_ID_REQUIRED in integration tests to catch callers that drop thread context.","Switch to scope: 'resource' if per-thread identity is not actually required."],"tags":["observational-memory","missing-argument","thread"],"backgroundTag":"missing-required-parameter","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}