{"record":{"id":"3bc156b841adb28f","repo":"mastra-ai/mastra","slug":"resourceid-and-threadid-are-required-to-persist-an","errorCode":null,"errorMessage":"resourceId and threadId are required to persist an active signal","messagePattern":"resourceId and threadId are required to persist an active signal","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/thread-stream-runtime.ts","lineNumber":2832,"sourceCode":"    const isActiveTarget = Boolean(\n      runId && (activeRecord?.output.status === 'running' || (key && state.activeThreadRunIds.get(key) === runId)),\n    );\n    let signal = createSignal({\n      ...signalInput,\n      id: signalInput.id ?? this.#generateSignalMessageId(agent, { resourceId, threadId }),\n      acceptedAt: new Date(),\n    });\n\n    // Resolve conditional delivery attributes now that we know the delivery path.\n    signal = resolveDeliveryAttributes(\n      signal,\n      isActiveTarget ? target.ifActive?.attributes : target.ifIdle?.attributes,\n    );\n\n    if (isActiveTarget && activeBehavior !== 'deliver') {\n      if (activeBehavior === 'persist') {\n        if (!resourceId || !threadId) {\n          throw new Error('resourceId and threadId are required to persist an active signal');\n        }\n        // Transient signals are never written to storage, so a `persist` behavior has nothing\n        // to do with them — report the drop honestly as `discard` instead of `persist`.\n        if (signal.transient) {\n          return {\n            signal,\n            accepted: Promise.resolve({ action: 'discard' as const }),\n          };\n        }\n        const persisted = this.#persistSignal(\n          agent,\n          signal,\n          resourceId,\n          threadId,\n          target.ifIdle?.streamOptions?.requestContext,\n        );\n        void persisted.catch(() => {});\n        return {","sourceCodeStart":2814,"sourceCodeEnd":2850,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent/thread-stream-runtime.ts#L2814-L2850","documentation":"Thrown when a signal targets an ACTIVE run with behavior 'persist' but resourceId/threadId could not be resolved, so the runtime cannot write the signal to storage. Note that for transient signals the runtime intentionally reports a 'discard' result instead, since transient signals are never persisted.","triggerScenarios":"Calling sendSignal with ifActive: { behavior: 'persist' } on an active run while resourceId/threadId are missing from both the target and the active record.","commonSituations":"Using behavior 'persist' without configuring thread context; mismatch between signal options and how the run was started (run started without thread IDs); constructing signal options dynamically where the IDs got dropped.","solutions":["Always include resourceId and threadId when using ifActive behavior 'persist'.","Use ifActive: { behavior: 'deliver' } if you don't need storage-backed delivery — it doesn't require resolvable thread IDs the same way.","Handle the returned result action: if you use transient signals with 'persist', expect the honest 'discard' result rather than an error."],"exampleFix":"// before\nawait runtime.sendSignal(agent, input, { runId, ifActive: { behavior: 'persist' } });\n// after\nawait runtime.sendSignal(agent, input, { runId, resourceId: 'user-1', threadId: 'thread-1', ifActive: { behavior: 'persist' } });","handlingStrategy":"validation","validationCode":"if (target.ifActive?.behavior === 'persist' && !(target.resourceId && target.threadId)) {\n  throw new Error('persist behavior requires resourceId and threadId');\n}","typeGuard":"null","tryCatchPattern":"try {\n  return await runtime.sendSignal(agent, input, target);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('required to persist an active signal')) {\n    // retry with 'deliver' behavior or supply thread context\n  }\n  throw e;\n}","preventionTips":["Pair behavior:'persist' with full thread context by convention/helper","Remember transient signals can't be persisted — expect 'discard' result","Prefer 'deliver' when storage-backed delivery isn't required"],"tags":["agent","signal","memory","missing-argument"],"backgroundTag":"missing-thread-context","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}