mongodb/node-mongodb-native · error · MongoAPIError
Cannot rewind cursor that does not own its timeout context.
Error message
Cannot rewind cursor that does not own its timeout context.
What it means
Thrown as a MongoAPIError from rewind() when the cursor has a timeoutContext whose owner is not this cursor (`this.timeoutContext.owner !== this`). When a cursor is created inside another operation (e.g. an aggregation run under a parent CSOT timeout context), the timeout context is shared/owned externally; rewinding would need to clear that context, but doing so would corrupt the parent's deadline. The driver therefore forbids rewinding a cursor that borrowed its timeout context. The check sits at the top of rewind(), before the not-initialized early return.
Solutions
- Do not call rewind() on cursors created under a CSOT/timeoutMS budget; create a fresh cursor via collection.find()/aggregate() instead.
- If you need to re-run the query, drop rewind() and build a new cursor with the same options.
- If you must rewind, ensure no timeoutMS/timeoutContext is in effect on the client or the cursor.
Example fix
// before: rewind under a shared timeout context
const cursor = collection.find(filter, { timeoutMS: 1000 });
await cursor.next();
cursor.rewind(); // throws if the cursor borrowed a timeout context
// after: create a fresh cursor instead of rewinding
const cursor2 = collection.find(filter, { timeoutMS: 1000 }); Defensive patterns
Strategy: validation
Validate before calling
// Avoid rewind() entirely under CSOT; create a fresh cursor instead
function rerun(cursor: FindCursor) {
// safe: brand-new cursor with the same filter/options
return cursor.client.db(cursor.namespace.db).collection(cursor.namespace.collection).find(filter, opts);
} Try / catch
try {
cursor.rewind();
} catch (err) {
if (err instanceof MongoAPIError && /does not own its timeout context/.test(err.message)) {
// create a fresh cursor instead of rewinding
cursor = collection.find(filter, opts);
} else {
throw err;
}
} Prevention
- Prefer creating a new cursor over rewind() when timeoutMS/CSOT is in use.
- Do not inject a shared timeoutContext unless you own the cursor lifecycle.
- Audit rewind() usage when adopting CSOT.
When it happens
Trigger: Calling cursor.rewind() on a cursor whose timeoutContext was injected via the internal timeoutContext option (typically by the driver itself when nesting operations under CSOT); rewinding a cursor obtained from an operation that ran under an explicit timeoutMS on the client/command.
Common situations: Mixing manual rewind() usage with CSOT (timeoutMS) on a client configured with a global timeout; calling rewind() on a cursor returned by an internal helper that injected a shared timeout context.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Cannot set tailable cursor's timeoutMode to LIFETIME
- Cannot set timeoutMode without setting timeoutMS
- Cannot specify maxAwaitTimeMS >= timeoutMS for a tailable…
- Cannot use $out or $merge stage with ITERATION timeoutMode
- Cursor must be constructed with MongoClient
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/b4d94998d03ecd6b.
Report an issue: GitHub.
Appendix: source
Thrown at src/cursor/abstract_cursor.ts:822
throw new MongoTailableCursorError('Tailable cursor does not support batchSize');
}
if (typeof value !== 'number') {
throw new MongoInvalidArgumentError('Operation "batchSize" requires an integer');
}
this.cursorOptions.batchSize = value;
return this;
}
/**
* Rewind this cursor to its uninitialized state. Any options that are present on the cursor will
* remain in effect. Iterating this cursor will cause new queries to be sent to the server, even
* if the resultant data has already been retrieved by this cursor.
*/
rewind(): void {
if (this.timeoutContext && this.timeoutContext.owner !== this) {
throw new MongoAPIError(`Cannot rewind cursor that does not own its timeout context.`);
}
if (!this.initialized) {
return;
}
this.cursorId = null;
this.documents?.clear();
this.timeoutContext?.clear();
this.timeoutContext = undefined;
this.isClosed = false;
this.isKilled = false;
this.initialized = false;
this.hasEmittedClose = false;
this.trackCursor();
// We only want to end this session if we created it, and it hasn't ended yet
if (this.cursorSession?.explicit === false) {
if (!this.cursorSession.hasEnded) {View on GitHub (pinned to dce7939f86)