{"record":{"id":"435200be33668225","repo":"mastra-ai/mastra","slug":"resourceid-and-threadid-are-required-when-runid-is","errorCode":null,"errorMessage":"resourceId and threadId are required when runId is not provided","messagePattern":"resourceId and threadId are required when runId is not provided","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/agents.ts","lineNumber":1986,"sourceCode":"          runId,\n          ...(effectiveResourceId ? { resourceId: effectiveResourceId } : {}),\n          ...(effectiveThreadId ? { threadId: effectiveThreadId } : {}),\n          ...(ifActive ? { ifActive } : {}),\n        });\n        // `accepted` resolves once the runtime decides how to route the signal; it only\n        // rejects on a setup/misconfig failure (e.g. no model), which `handleError` maps\n        // below. `runId` is present on `wake`/`deliver` (a run exists); `persist`/`discard`\n        // never start a run, so we fall back to the caller's `runId` to keep the wire\n        // contract (`runId: string`) stable.\n        const settled = await result.accepted;\n        const settledRunId = 'runId' in settled ? settled.runId : runId;\n        return result.signal === undefined\n          ? { accepted: true as const, runId: settledRunId }\n          : { accepted: true as const, runId: settledRunId, signal: result.signal };\n      }\n\n      if (!effectiveResourceId || !effectiveThreadId) {\n        throw new HTTPException(400, { message: 'resourceId and threadId are required when runId is not provided' });\n      }\n\n      const result = await agent.sendSignal(agentSignal, {\n        resourceId: effectiveResourceId,\n        threadId: effectiveThreadId,\n        ...(ifActive ? { ifActive } : {}),\n        ...ifIdleWithContext,\n      });\n      // `accepted` carries the authoritative `runId` for `wake`/`deliver` (a run exists).\n      // `persist`/`discard` never start a run; the stored-message id (`result.signal.id`)\n      // is the correlatable id for those, keeping the wire contract (`runId: string`) stable.\n      const settled = await result.accepted;\n      const settledRunId = 'runId' in settled ? settled.runId : result.signal?.id;\n      return result.signal === undefined\n        ? { accepted: true as const, runId: settledRunId }\n        : { accepted: true as const, runId: settledRunId, signal: result.signal };\n    } catch (error) {\n      return handleSignalRoutingError(error, 'error sending agent signal');","sourceCodeStart":1968,"sourceCodeEnd":2004,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/agents.ts#L1968-L2004","documentation":"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.","triggerScenarios":"POSTing a signal without runId and with either resourceId or threadId (or both) absent from the request body/context.","commonSituations":"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.","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"],"exampleFix":"// before\nawait client.getAgent('my-agent').signal({ type: 'user-input', payload: data });\n// after\nawait client.getAgent('my-agent').signal(\n  { type: 'user-input', payload: data },\n  { resourceId: 'user-1', threadId: 'thread-42' }\n);","handlingStrategy":"validation","validationCode":"function assertSignalTarget(params: { runId?: string; resourceId?: string; threadId?: string }) {\n  if (!params.runId && (!params.resourceId || !params.threadId)) {\n    throw new Error('resourceId and threadId are required when runId is not provided');\n  }\n}","typeGuard":"function hasSignalTarget(p: { runId?: string; resourceId?: string; threadId?: string }): p is typeof p & ({ runId: string } | { resourceId: string; threadId: string }) {\n  return Boolean(p.runId || (p.resourceId && p.threadId));\n}","tryCatchPattern":"try {\n  await signalAgent(signal, target);\n} catch (e) {\n  if (e?.status === 400 && /resourceId and threadId/.test(e.message)) {\n    target = await resolveThreadForAgent(agentId);\n    return signalAgent(signal, target);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["http-400","missing-parameter","agent-signals","server"],"backgroundTag":"missing-required-parameter","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}