Automattic/mongoose · error · MongooseError

Cannot set a document's session to a session that has ended.

Error message

Cannot set a document's session to a session that has ended. Make sure you haven't called `endSession()` on the session you are passing to `$session()`.

What it means

$session(session) attaches a ClientSession to the document (and propagates it to subdocuments) so subsequent saves run inside it. Mongoose rejects sessions whose `hasEnded` flag is set: after endSession() the session can carry no further operations, so attaching one would guarantee driver failures on every later command.

Source

Thrown at lib/document.js:985

 *
 * @param {ClientSession} [session] overwrite the current session
 * @return {ClientSession}
 * @method $session
 * @api public
 * @memberOf Document
 */

Document.prototype.$session = function $session(session) {
  if (arguments.length === 0) {
    if (this.$__.session?.hasEnded) {
      this.$__.session = null;
      return null;
    }
    return this.$__.session;
  }

  if (session?.hasEnded) {
    throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' +
      'called `endSession()` on the session you are passing to `$session()`.');
  }

  if (session == null && this.$__.session == null) {
    return;
  }

  this.$__.session = session;

  if (!this.$isSubdocument) {
    const subdocs = this.$getAllSubdocs();
    for (const child of subdocs) {
      child.$session(session);
    }
  }

  return session;
};

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Call `doc.$session(null)` once the session's work is done, and start a fresh session with conn.startSession() for later operations
  2. Move endSession() to after the last operation that uses the session (end of transaction/request)
  3. Guard every attach: `if (session != null && !session.hasEnded) doc.$session(session)`

Example fix

// before
await session.endSession();
doc.$session(session); // throws: session already ended

// after
await session.endSession();
doc.$session(null);
const s2 = await conn.startSession();
doc.$session(s2);
Defensive patterns

Strategy: validation

Validate before calling

function attachSession(doc, session) {
  if (session != null && session.hasEnded) {
    throw new Error('Refusing to attach an ended session; start a new one');
  }
  doc.$session(session ?? null);
}

Type guard

/** @param {import('mongodb').ClientSession | null | undefined} s */
const isUsableSession = (s) => s == null || s.hasEnded !== true;

Try / catch

try {
  doc.$session(s);
} catch (err) {
  if (/session that has ended/.test(err.message)) {
    doc.$session(null);
    s = await conn.startSession();
    doc.$session(s);
  } else { throw err; }
}

Prevention

When it happens

Trigger: `const s = await conn.startSession(); await s.endSession(); doc.$session(s)`; storing a session on a document past the end of a withTransaction()/withSession() block (both end the session when their callback resolves); sessions ended by connection.close().

Common situations: Session lifecycle owned by a request wrapper while documents outlive the request; caching or pooling ClientSession objects; ending the session in a finally block before the last save() has run.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/95f9bbe01f85ec0f. Report an issue: GitHub.