{"record":{"id":"b4d94998d03ecd6b","repo":"mongodb/node-mongodb-native","slug":"cannot-rewind-cursor-that-does-not-own-its-timeout","errorCode":null,"errorMessage":"Cannot rewind cursor that does not own its timeout context.","messagePattern":"Cannot rewind cursor that does not own its timeout context\\.","errorType":"exception","errorClass":"MongoAPIError","httpStatus":null,"severity":"error","filePath":"src/cursor/abstract_cursor.ts","lineNumber":822,"sourceCode":"      throw new MongoTailableCursorError('Tailable cursor does not support batchSize');\n    }\n\n    if (typeof value !== 'number') {\n      throw new MongoInvalidArgumentError('Operation \"batchSize\" requires an integer');\n    }\n\n    this.cursorOptions.batchSize = value;\n    return this;\n  }\n\n  /**\n   * Rewind this cursor to its uninitialized state. Any options that are present on the cursor will\n   * remain in effect. Iterating this cursor will cause new queries to be sent to the server, even\n   * if the resultant data has already been retrieved by this cursor.\n   */\n  rewind(): void {\n    if (this.timeoutContext && this.timeoutContext.owner !== this) {\n      throw new MongoAPIError(`Cannot rewind cursor that does not own its timeout context.`);\n    }\n    if (!this.initialized) {\n      return;\n    }\n\n    this.cursorId = null;\n    this.documents?.clear();\n    this.timeoutContext?.clear();\n    this.timeoutContext = undefined;\n    this.isClosed = false;\n    this.isKilled = false;\n    this.initialized = false;\n    this.hasEmittedClose = false;\n    this.trackCursor();\n\n    // We only want to end this session if we created it, and it hasn't ended yet\n    if (this.cursorSession?.explicit === false) {\n      if (!this.cursorSession.hasEnded) {","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/cursor/abstract_cursor.ts#L804-L840","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: rewind under a shared timeout context\nconst cursor = collection.find(filter, { timeoutMS: 1000 });\nawait cursor.next();\ncursor.rewind(); // throws if the cursor borrowed a timeout context\n\n// after: create a fresh cursor instead of rewinding\nconst cursor2 = collection.find(filter, { timeoutMS: 1000 });","handlingStrategy":"validation","validationCode":"// Avoid rewind() entirely under CSOT; create a fresh cursor instead\nfunction rerun(cursor: FindCursor) {\n  // safe: brand-new cursor with the same filter/options\n  return cursor.client.db(cursor.namespace.db).collection(cursor.namespace.collection).find(filter, opts);\n}","typeGuard":null,"tryCatchPattern":"try {\n  cursor.rewind();\n} catch (err) {\n  if (err instanceof MongoAPIError && /does not own its timeout context/.test(err.message)) {\n    // create a fresh cursor instead of rewinding\n    cursor = collection.find(filter, opts);\n  } else {\n    throw err;\n  }\n}","preventionTips":["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."],"tags":["cursor","rewind","csot","timeout-context","internal-misuse"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}