{"record":{"id":"f2f232267d7fdb38","repo":"mastra-ai/mastra","slug":"resolvesession-returned-a-session-for-resourceid","errorCode":null,"errorMessage":"resolveSession returned a session for resourceId=\"${session.identity.getResourceId()}\", but the mapped channel thread belongs to resourceId=\"${channelResourceId}\". A session can only bind threads it owns — resolve the session under the thread's resourceId, or map the thread onto the session's resourceId with the channels `resolveResourceId` option.","messagePattern":"resolveSession returned a session for resourceId=\"(.+?)\", but the mapped channel thread belongs to resourceId=\"(.+?)\"\\. A session can only bind threads it owns — resolve the session under the thread's resourceId, or map the thread onto the session's resourceId with the channels `resolveResourceId` option\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/channels/agent-controller-channels.ts","lineNumber":432,"sourceCode":"    // exists. It gets the same controller so this channels instance stays the\n    // owner of outbound delivery for whatever session it returns. A throw is\n    // tagged as a refusal so the channel error boundary keeps it out of the\n    // chat thread instead of posting the host's authorization message there.\n    const session = this.resolveSession\n      ? await this.resolveChannelSession({ controller, thread, requestContext })\n      : await controller.createSession({\n          resourceId: channelResourceId,\n          id: channelResourceId,\n          ownerId: controller.id,\n          requestContext,\n        });\n    // A session may only bind threads it owns, and the mapped thread's owner is\n    // fixed by the channel's resourceId mapping. Catching the mismatch here\n    // turns an opaque \"Thread not found\" from deep inside the session into the\n    // actual instruction: line the two up, which hosts do by pairing this\n    // resolver with `resolveResourceId`.\n    if (this.resolveSession && session.identity.getResourceId() !== channelResourceId) {\n      throw new Error(\n        `resolveSession returned a session for resourceId=\"${session.identity.getResourceId()}\", but the mapped channel thread belongs to resourceId=\"${channelResourceId}\". A session can only bind threads it owns — resolve the session under the thread's resourceId, or map the thread onto the session's resourceId with the channels \\`resolveResourceId\\` option.`,\n      );\n    }\n    // Bind the mapped thread. Guard is mandatory: `switch` aborts any active\n    // run, so never re-switch when the session is already on this thread.\n    if (session.thread.getId() !== thread.id) {\n      await session.thread.switch({ threadId: thread.id });\n    }\n    await this.runSessionStartHook(session, thread, requestContext);\n    return session;\n  }\n\n  /**\n   * Call the host's resolver and tag anything it throws as a refusal. Covers\n   * synchronous throws too — the hook may return a `Session` directly, so a\n   * plain `.catch()` on the return value would miss them.\n   */\n  private async resolveChannelSession(ctx: ChannelSessionResolveContext): Promise<Session<any>> {","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/channels/agent-controller-channels.ts#L414-L450","documentation":"When a custom resolveSession is configured on channels, the session it returns must be bound to the same resourceId that the channel thread mapping resolved to. If session.identity.getResourceId() differs from the thread's channel resourceId, binding would violate ownership (a session may only bind threads it owns), so this explicit mismatch error is thrown instead of an opaque 'Thread not found' deeper in the stack.","triggerScenarios":"getSessionForThread with this.resolveSession set, where the custom resolver creates/returns sessions under a resourceId (e.g. user ID) different from the thread's channel-resolved resourceId (e.g. platform-wide or channel ID), and resolveResourceId is not configured to align them.","commonSituations":"Custom session resolvers keyed on end-user identity while channel threads are mapped to a workspace/channel resourceId; migrating from default session resolution to a custom resolver without updating resourceId mapping.","solutions":["Configure the channel's `resolveResourceId` option so the thread maps onto the same resourceId the session resolver uses","Change the custom resolveSession to resolve sessions under the thread's resourceId (channelResourceId)","Unify resourceId derivation in one helper used by both the resolver and the channel mapping"],"exampleFix":"// before\nresolveSession: async ({ memory }) => sessionFor(memory, userId), // resourceId=userId\n// after\nnew AgentChannels({\n  resolveSession: async ({ memory }) => sessionFor(memory, channelResourceId),\n  resolveResourceId: ({ channelThreadId }) => channelResourceIdFor(channelThreadId),\n})","handlingStrategy":"validation","validationCode":"const session = await resolveSession(args);\nconst sessionResourceId = session.identity.getResourceId();\nif (sessionResourceId !== channelResourceId) {\n  throw new Error(`resolveSession resourceId ${sessionResourceId} != channel resourceId ${channelResourceId}; align via resolveResourceId`);\n}","typeGuard":"function ownsThread(session: { identity: { getResourceId(): string } }, channelResourceId: string): boolean {\n  return session.identity.getResourceId() === channelResourceId;\n}","tryCatchPattern":"try {\n  const session = await channels.getSessionForThread(args);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('can only bind threads it owns')) {\n    logger.error({ sessionResourceId, channelResourceId }, 'resourceId mismatch in resolveSession');\n  } else throw err;\n}","preventionTips":["Derive resourceId for sessions and channel threads from one shared helper","Configure channels `resolveResourceId` when using custom resolveSession","Write an integration test resolving a session for every mapped thread shape"],"tags":["channels","resource-id","session-ownership"],"backgroundTag":"resource-id-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}