mastra-ai/mastra · error
resourceId and threadId are required to queue a message
Error message
resourceId and threadId are required to queue a message
What it means
AgentThreadStreamRuntime.queueMessage() throws this when it cannot resolve both a resourceId and a threadId for the signal target. Normally these come from the options object, but when omitted the runtime tries to infer them from an active run record (looked up by runId or thread key). If neither the options nor an active record yields both IDs, it cannot address a thread and throws.
Source
Thrown at packages/core/src/agent/thread-stream-runtime.ts:2670
activeRecord = activeRunId ? state.threadRunsById.get(activeRunId) : undefined;
if (activeRecord && !this.#isThreadBlockingRun(state, activeRecord)) {
state.activeThreadRunIds.delete(key);
activeRecord = undefined;
}
runId ??= activeRunId;
}
if (runId) {
activeRecord ??= state.threadRunsById.get(runId);
if (activeRecord) {
key ??= this.#threadKey(activeRecord.resourceId, activeRecord.threadId);
}
}
const resourceId = target.resourceId ?? activeRecord?.resourceId;
const threadId = target.threadId ?? activeRecord?.threadId;
if (!resourceId || !threadId) {
throw new Error('resourceId and threadId are required to queue a message');
}
key ??= this.#threadKey(resourceId, threadId);
const signal = createMessageSignal(message, {
id: this.#generateSignalMessageId(agent, { resourceId, threadId }),
acceptedAt,
});
const queuedRunId = randomUUID();
const queuedStreamOptions = target.ifIdle?.streamOptions ?? activeRecord?.streamOptions;
if (activeRecord) {
const idleQueue = state.pendingIdleSignalsByThread.get(key) ?? [];
idleQueue.push({ agent, signal, runId: queuedRunId, resourceId, threadId, streamOptions: queuedStreamOptions });
state.pendingIdleSignalsByThread.set(key, idleQueue);
this.#watchThreadRunCompletion(state, pubsub, key, activeRecord);
return {
signal,
accepted: Promise.resolve({ action: 'deliver' as const, runId: queuedRunId }),View on GitHub (pinned to 75dd419e61)
Solutions
- Always pass both resourceId and threadId in the QueueAgentMessageOptions target object.
- If you only have a runId, verify the run is still active (registered in the runtime state) before calling queueMessage, or capture resourceId/threadId when the run started.
- Wrap the call in try/catch and fall back to agent.generate/stream directly when no thread context exists.
Example fix
// before
await runtime.queueMessage(agent, message, {});
// after
await runtime.queueMessage(agent, message, { resourceId: 'user-1', threadId: 'thread-1' }); Defensive patterns
Strategy: validation
Validate before calling
function canQueueMessage(target) {
return Boolean(target.resourceId && target.threadId);
}
if (!canQueueMessage(target)) throw new Error('queueMessage needs resourceId and threadId'); Type guard
function hasThreadTarget(t: { resourceId?: string; threadId?: string }): t is { resourceId: string; threadId: string } {
return typeof t.resourceId === 'string' && t.resourceId.length > 0 && typeof t.threadId === 'string' && t.threadId.length > 0;
} Try / catch
try {
return runtime.queueMessage(agent, message, target);
} catch (e) {
if (e instanceof Error && e.message.includes('required to queue a message')) {
// no resolvable thread context — fall back to direct generation or rethrow with context
}
throw e;
} Prevention
- Always construct signal targets from a helper that requires resourceId/threadId
- Type the options narrowly instead of optional fields when a thread target is mandatory
- Capture resourceId/threadId when starting a run and reuse them for signaling
- Log the target object before calling to catch dropped fields in refactors
When it happens
Trigger: Calling runtime.queueMessage(agent, message, target) with (a) neither target.resourceId nor target.threadId set, and (b) no target.runId matching an active run record whose resourceId/threadId can be inferred.
Common situations: Building a custom pubsub/signal integration and passing only runId after the run already completed (record evicted from state); copying options objects and dropping resourceId/threadId; calling queueMessage from a context where only a message is available.
Related errors
- resourceId and threadId are required to send a state signal
- resourceId and threadId are required to persist an active si
- 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/7381815abb7a2279.
Report an issue: GitHub.