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
- Clone before re-running: `await q.clone().exec()`.
- Rebuild the query from a factory function each time instead of storing the Query object.
- 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
- Store query factory functions, not Query instances, for reuse.
- Use q.clone() for every re-run.
- Create fresh queries inside retry loops.
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
- Document.prototype.validate() no longer accepts a callback
- Document.prototype.populate() no longer accepts a callback
- Model.prototype.save() no longer accepts a callback
- Model.prototype.deleteOne() no longer accepts a callback
- Model.exists() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/9494a4c845424523.
Report an issue: GitHub.