{"record":{"id":"7127cf8db3c18e24","repo":"mastra-ai/mastra","slug":"both-threadid-or-resourceid-must-be-provided","errorCode":null,"errorMessage":"Both threadId or resourceId must be provided","messagePattern":"Both threadId or resourceId must be provided","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/agents.ts","lineNumber":1508,"sourceCode":"        requestContext,\n      });\n\n      // UI Frameworks may send \"client tools\" in the body,\n      // but it interferes with llm providers tool handling, so we remove them\n      sanitizeBody(params, ['tools', 'actor']);\n\n      const { messages, resourceId, resourceid, threadId, ...rest } = params;\n      // Use resourceId if provided, fall back to resourceid (deprecated)\n      const clientResourceId = resourceId ?? resourceid;\n\n      // Authorization: context values take precedence over client-provided values\n      const effectiveResourceId = getEffectiveResourceId(requestContext, clientResourceId);\n      const effectiveThreadId = getEffectiveThreadId(requestContext, threadId);\n\n      validateBody({ messages });\n\n      if ((effectiveThreadId && !effectiveResourceId) || (!effectiveThreadId && effectiveResourceId)) {\n        throw new HTTPException(400, { message: 'Both threadId or resourceId must be provided' });\n      }\n\n      // Validate thread ownership if accessing an existing thread\n      if (effectiveThreadId) {\n        const memory = await agent.getMemory({ requestContext });\n        if (memory) {\n          const thread = await memory.getThreadById({ threadId: effectiveThreadId });\n          if (thread) {\n            await enforceThreadAccess({\n              mastra,\n              requestContext,\n              threadId: effectiveThreadId,\n              thread,\n              effectiveResourceId,\n              permission: MastraFGAPermissions.MEMORY_WRITE,\n            });\n          }\n        }","sourceCodeStart":1490,"sourceCodeEnd":1526,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/agents.ts#L1490-L1526","documentation":"Thrown by the deprecated POST /agents/:agentId/generate-legacy route handler when exactly one of threadId/resourceId is provided. Memory-scoped generation requires both together: threadId identifies the conversation thread and resourceId identifies its owning resource, so a lone value is rejected with 400.","triggerScenarios":"Calling generate-legacy with threadId but no resourceId (or vice versa) in the body — remembering that requestContext can supply the effective value, so a body resourceId paired with a threadId-only requestContext (or the reverse) also triggers it.","commonSituations":"Older clients that only sent threadId; code migrated from an API where resourceId was optional; server middleware injecting resourceId via requestContext while the client still sends only threadId, leaving the other half unset.","solutions":["Send both threadId and resourceId together in the request body (or supply the missing half via requestContext)","If you don't need memory, omit both fields and call without thread persistence","Migrate off the deprecated generate-legacy route to /agents/:agentId/generate with matching current parameter handling"],"exampleFix":"// before\nawait client.generate({ threadId: 'thread-1' }); // resourceId missing\n// after\nawait client.generate({ threadId: 'thread-1', resourceId: 'user-42' });","handlingStrategy":"validation","validationCode":"function validateMemoryParams(p: { threadId?: string; resourceId?: string }): void {\n  const hasThread = Boolean(p.threadId);\n  const hasResource = Boolean(p.resourceId);\n  if (hasThread !== hasResource) {\n    throw new Error('threadId and resourceId must be provided together');\n  }\n}","typeGuard":"function hasCompleteMemoryPair(p: { threadId?: string; resourceId?: string }): p is { threadId: string; resourceId: string } {\n  return typeof p.threadId === 'string' && p.threadId.length > 0 &&\n         typeof p.resourceId === 'string' && p.resourceId.length > 0;\n}","tryCatchPattern":"try {\n  await client.generateLegacy({ messages, threadId });\n} catch (e) {\n  if (isHttpException(e, 400) && String(e.message).includes('threadId or resourceId')) {\n    // add the missing half or drop both\n  }\n}","preventionTips":["Always pass threadId and resourceId as a pair from a shared memory-context object","When relying on requestContext-injected values, verify both halves resolve before the call","Migrate off generate-legacy to the current generate route with pair validation in the client SDK"],"tags":["http-400","validation","memory","legacy-api"],"backgroundTag":"missing-required-parameter","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}