mastra-ai/mastra · error

resourceId and threadId are required to persist an active si

Error message

resourceId and threadId are required to persist an active signal

What it means

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.

Source

Thrown at packages/core/src/agent/thread-stream-runtime.ts:2832

    const isActiveTarget = Boolean(
      runId && (activeRecord?.output.status === 'running' || (key && state.activeThreadRunIds.get(key) === runId)),
    );
    let signal = createSignal({
      ...signalInput,
      id: signalInput.id ?? this.#generateSignalMessageId(agent, { resourceId, threadId }),
      acceptedAt: new Date(),
    });

    // Resolve conditional delivery attributes now that we know the delivery path.
    signal = resolveDeliveryAttributes(
      signal,
      isActiveTarget ? target.ifActive?.attributes : target.ifIdle?.attributes,
    );

    if (isActiveTarget && activeBehavior !== 'deliver') {
      if (activeBehavior === 'persist') {
        if (!resourceId || !threadId) {
          throw new Error('resourceId and threadId are required to persist an active signal');
        }
        // Transient signals are never written to storage, so a `persist` behavior has nothing
        // to do with them — report the drop honestly as `discard` instead of `persist`.
        if (signal.transient) {
          return {
            signal,
            accepted: Promise.resolve({ action: 'discard' as const }),
          };
        }
        const persisted = this.#persistSignal(
          agent,
          signal,
          resourceId,
          threadId,
          target.ifIdle?.streamOptions?.requestContext,
        );
        void persisted.catch(() => {});
        return {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Always include resourceId and threadId when using ifActive behavior 'persist'.
  2. Use ifActive: { behavior: 'deliver' } if you don't need storage-backed delivery — it doesn't require resolvable thread IDs the same way.
  3. Handle the returned result action: if you use transient signals with 'persist', expect the honest 'discard' result rather than an error.

Example fix

// before
await runtime.sendSignal(agent, input, { runId, ifActive: { behavior: 'persist' } });
// after
await runtime.sendSignal(agent, input, { runId, resourceId: 'user-1', threadId: 'thread-1', ifActive: { behavior: 'persist' } });
Defensive patterns

Strategy: validation

Validate before calling

if (target.ifActive?.behavior === 'persist' && !(target.resourceId && target.threadId)) {
  throw new Error('persist behavior requires resourceId and threadId');
}

Type guard

null

Try / catch

try {
  return await runtime.sendSignal(agent, input, target);
} catch (e) {
  if (e instanceof Error && e.message.includes('required to persist an active signal')) {
    // retry with 'deliver' behavior or supply thread context
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling sendSignal with ifActive: { behavior: 'persist' } on an active run while resourceId/threadId are missing from both the target and the active record.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/3bc156b841adb28f. Report an issue: GitHub.