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
- 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.
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
- 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
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
- resourceId and threadId are required to queue a message
- resourceId and threadId are required to send a state signal
- sendStateSignal requires Mastra memory
- No active agent run found for signal target
- Could not generate title from input ${JSON.stringify(message
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/3bc156b841adb28f.
Report an issue: GitHub.