mastra-ai/mastra · error · HTTPException
resourceId and threadId are required when runId is not provi
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.
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)
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 parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.
Related errors
- threadId is required
- Run id is required
- Agent ID is required
- Cannot delete the active version. Activate a different versi
- ${error.message}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/435200be33668225.
Report an issue: GitHub.