Automattic/mongoose · error · MongooseError

Query was already executed: ${str}

Error message

Query was already executed: ${str}

What it means

Since Mongoose 7 a Query instance tracks how many times it has executed (_execCount) and throws on re-execution. In Mongoose 6 re-running a query logged a warning; now each Query is single-use and you must clone it for a second run. This prevents subtle bugs where hooks, options, or cursor state leak between executions.

Source

Thrown at lib/query.js:4775

  if (this.model == null) {
    throw new MongooseError('Query must have an associated model before executing');
  }

  const thunk = opToThunk.get(this.op);
  if (!thunk) {
    throw new MongooseError('Query has invalid `op`: "' + this.op + '"');
  }

  if (this.options?.sort && typeof this.options.sort === 'object' && Object.hasOwn(this.options.sort, '')) {
    throw new MongooseError('Invalid field "" passed to sort()');
  }

  if (this._execCount > 0) {
    let str = this.toString();
    if (str.length > 60) {
      str = str.slice(0, 60) + '...';
    }
    throw new MongooseError('Query was already executed: ' + str);
  }
  this._execCount++;

  const _this = this;
  return traceQuery(async function maybeTracedQueryExec() {
    let skipWrappedFunction = null;
    try {
      await _this._hooks.execPre('exec', _this, []);
    } catch (err) {
      if (err instanceof Kareem.skipWrappedFunction) {
        skipWrappedFunction = err;
      } else {
        throw err;
      }
    }

    let res;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Clone before re-running: `await q.clone().exec()`.
  2. Rebuild the query from a factory function each time instead of storing the Query object.
  3. For retries, wrap the query creation inside the retry loop so each attempt gets a fresh query.

Example fix

// before
const q = Model.find({ status: 'active' });
const a = await q.exec();
const b = await q.exec(); // throws: Query was already executed

// after
const base = Model.find({ status: 'active' });
const a = await base.clone().exec();
const b = await base.clone().exec();
Defensive patterns

Strategy: fallback

Validate before calling

function runQuery(queryFactory) { return queryFactory().exec(); } // each call builds a new query
// or: if (q._execCount > 0) q = q.clone();

Try / catch

try { return await q.exec(); } catch (err) { if (err instanceof mongoose.Error && /already executed/.test(err.message)) { return q.clone().exec(); } throw err; }

Prevention

When it happens

Trigger: `const q = Model.find(); await q.exec(); await q.exec();`; sharing a module-level query constant across requests; loops that reuse one query variable; calling then() twice or awaiting a query after already awaiting it.

Common situations: Caching a built query and re-running it per request; retry logic that re-executes the same object; upgrading from Mongoose 5/6 where reuse merely warned.

Related errors


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