mastra-ai/mastra · error · HTTPException
resourceId and threadId are required when runId is not…
Error message
resourceId and threadId are required when runId is not provided
What it means
When no runId is supplied, the signal handler cannot attach the signal to an existing run, so it needs resourceId and threadId to locate the agent's thread and stream. If either is missing it throws HTTP 400. Supplying a runId bypasses this requirement entirely.
Solutions
- Pass both resourceId and threadId in the request body
- Alternatively pass the runId of the active run you want to signal
- Check any requestContext-based overrides — an undefined override silently nulls the effective value
Example fix
// before
await client.getAgent('my-agent').signal({ type: 'user-input', payload: data });
// after
await client.getAgent('my-agent').signal(
{ type: 'user-input', payload: data },
{ resourceId: 'user-1', threadId: 'thread-42' }
); Defensive patterns
Strategy: validation
Validate before calling
function assertSignalTarget(params: { runId?: string; resourceId?: string; threadId?: string }) {
if (!params.runId && (!params.resourceId || !params.threadId)) {
throw new Error('resourceId and threadId are required when runId is not provided');
}
} Type guard
function hasSignalTarget(p: { runId?: string; resourceId?: string; threadId?: string }): p is typeof p & ({ runId: string } | { resourceId: string; threadId: string }) {
return Boolean(p.runId || (p.resourceId && p.threadId));
} Try / catch
try {
await signalAgent(signal, target);
} catch (e) {
if (e?.status === 400 && /resourceId and threadId/.test(e.message)) {
target = await resolveThreadForAgent(agentId);
return signalAgent(signal, target);
}
throw e;
} Prevention
- Always keep resourceId/threadId from the original run in app state alongside runId
- Capture threadId from stream metadata at run start and persist it
- Validate the target object before every signal call
When it happens
Trigger: POSTing a signal without runId and with either resourceId or threadId (or both) absent from the request body/context.
Common situations: Client code that always sent runId before now omitting it; requestContext overrides (getEffectiveResourceId/getEffectiveThreadId) returning undefined because the value wasn't passed or configured; building the request from a partial thread object.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Agent ID is required
- Run id is required
- threadId is required
- agent signals are not supported by this Mastra core version
- Cannot delete the active version. Activate a different…
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/435200be33668225.
Report an issue: GitHub.
Appendix: source
Thrown at packages/server/src/server/handlers/agents.ts:1986
runId,
...(effectiveResourceId ? { resourceId: effectiveResourceId } : {}),
...(effectiveThreadId ? { threadId: effectiveThreadId } : {}),
...(ifActive ? { ifActive } : {}),
});
// `accepted` resolves once the runtime decides how to route the signal; it only
// rejects on a setup/misconfig failure (e.g. no model), which `handleError` maps
// below. `runId` is present on `wake`/`deliver` (a run exists); `persist`/`discard`
// never start a run, so we fall back to the caller's `runId` to keep the wire
// contract (`runId: string`) stable.
const settled = await result.accepted;
const settledRunId = 'runId' in settled ? settled.runId : runId;
return result.signal === undefined
? { accepted: true as const, runId: settledRunId }
: { accepted: true as const, runId: settledRunId, signal: result.signal };
}
if (!effectiveResourceId || !effectiveThreadId) {
throw new HTTPException(400, { message: 'resourceId and threadId are required when runId is not provided' });
}
const result = await agent.sendSignal(agentSignal, {
resourceId: effectiveResourceId,
threadId: effectiveThreadId,
...(ifActive ? { ifActive } : {}),
...ifIdleWithContext,
});
// `accepted` carries the authoritative `runId` for `wake`/`deliver` (a run exists).
// `persist`/`discard` never start a run; the stored-message id (`result.signal.id`)
// is the correlatable id for those, keeping the wire contract (`runId: string`) stable.
const settled = await result.accepted;
const settledRunId = 'runId' in settled ? settled.runId : result.signal?.id;
return result.signal === undefined
? { accepted: true as const, runId: settledRunId }
: { accepted: true as const, runId: settledRunId, signal: result.signal };
} catch (error) {
return handleSignalRoutingError(error, 'error sending agent signal');View on GitHub (pinned to 75dd419e61)