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

  1. Pass both resourceId and threadId in the request body
  2. Alternatively pass the runId of the active run you want to signal
  3. 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

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


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