{"record":{"id":"7640f1cff3fa88d3","repo":"mongodb/node-mongodb-native","slug":"cursor-is-exhausted","errorCode":null,"errorMessage":"Cursor is exhausted","messagePattern":"Cursor is exhausted","errorType":"exception","errorClass":"MongoCursorExhaustedError","httpStatus":null,"severity":"error","filePath":"src/cursor/abstract_cursor.ts","lineNumber":553,"sourceCode":"          return true;\n        }\n        await this.fetchBatch();\n      } while (!this.isDead || (this.documents?.length ?? 0) !== 0);\n    } finally {\n      if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {\n        this.timeoutContext?.clear();\n      }\n    }\n\n    return false;\n  }\n\n  /** Get the next available document from the cursor, returns null if no more documents are available. */\n  async next(): Promise<TSchema | null> {\n    this.signal?.throwIfAborted();\n\n    if (this.cursorId === Long.ZERO) {\n      throw new MongoCursorExhaustedError();\n    }\n\n    if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION && this.cursorId != null) {\n      this.timeoutContext?.refresh();\n    }\n\n    try {\n      do {\n        const doc = this.documents?.shift(this.deserializationOptions);\n        if (doc != null) {\n          if (this.transform != null) return await this.transformDocument(doc);\n          return doc;\n        }\n        await this.fetchBatch();\n      } while (!this.isDead || (this.documents?.length ?? 0) !== 0);\n    } finally {\n      if (this.cursorOptions.timeoutMode === CursorTimeoutMode.ITERATION) {\n        this.timeoutContext?.clear();","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/cursor/abstract_cursor.ts#L535-L571","documentation":"Thrown as a MongoCursorExhaustedError (default message 'Cursor is exhausted') from next() when cursorId equals Long.ZERO at entry. A zero cursor id means the server already returned all results in the firstBatch and the cursor is closed server-side, so there is nothing more to fetch. next() is meant to return null for end-of-results during normal iteration, but calling it again after the cursor is already known-exhausted is treated as a logic error. The same guard exists in tryNext() at line 585.","triggerScenarios":"Calling cursor.next() after a previous next()/hasNext()/for-await loop already drained the cursor and set cursorId to Long.ZERO; calling next() on a cursor whose initial command returned a zero id and a full firstBatch.","commonSituations":"Manual next() loops without checking the return value for null; sharing a cursor across consumers where one finishes it and another calls next(); reusing a cursor stored in a long-lived variable after it has been fully read.","solutions":["Treat a `null` return from next() as end-of-stream and stop calling next().","Prefer `for await (const doc of cursor)` which handles exhaustion cleanly.","If you need repeat reads, call cursor.rewind() (when allowed) or create a fresh cursor via collection.find() again.","Guard with `if (!cursor.closed)` before manual next() calls."],"exampleFix":"// before: keeps calling next() past exhaustion\nwhile (true) {\n  const doc = await cursor.next(); // throws on the call after null\n}\n\n// after: stop on null (or just use for-await)\nlet doc;\nwhile ((doc = await cursor.next()) !== null) {\n  handle(doc);\n}","handlingStrategy":"validation","validationCode":"// Stop calling next() once the cursor is exhausted\nif (!cursor.closed) {\n  const doc = await cursor.next();\n  if (doc === null) return; // end of stream\n}","typeGuard":null,"tryCatchPattern":"try {\n  const doc = await cursor.next();\n} catch (err) {\n  if (err instanceof MongoCursorExhaustedError) {\n    // cursor already drained; stop iterating\n    return;\n  }\n  throw err;\n}","preventionTips":["Prefer for-await, which stops cleanly at exhaustion.","Treat a null return from next() as end-of-stream and stop.","Do not share a single cursor across multiple consumers."],"tags":["cursor","exhausted","iteration","logic-error"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}