Skip to content

Ordered bulkSave marks unattempted documents as saved after a write error #16532

Closed

Description

@IbrahimHafez1

Prerequisites

  • I have written a descriptive issue title.
  • I searched existing issues and open/closed pull requests.

Mongoose version

master at 940de5d2c62dc29104b664a0e15be8d4aae38e91 (package version 9.10.2)

Node.js version

22.17.1, Windows

MongoDB server version

8.2.3

Description

With default ordered bulkSave(), a write error stops MongoDB from attempting later operations. Mongoose nevertheless treats those later documents as successfully saved because they have no individual write error. It clears their modified state, marks new documents as no longer new, and runs post-save hooks even though their writes never ran.

This loses the pending changes from Mongoose's tracking and prevents a straightforward retry from inserting an unattempted document. The bulk promise does reject; the bug is the incorrect document state and success hooks after that rejection.

Steps to Reproduce

Run against a local disposable MongoDB database. The example uses a fresh collection name so previous runs do not affect the unique constraint.

const mongoose = require('mongoose');

async function main() {
  await mongoose.connect('mongodb://127.0.0.1:27017/bulk_save_repro');
  try {
    const schema = new mongoose.Schema({
      name: { type: String, unique: true }
    }, { collection: `bulk_save_repro_${Date.now()}` });
    const saved = [];
    schema.post('save', document => { saved.push(document.name); });
    const Item = mongoose.model('BulkSaveRepro', schema);
    await Item.init();

    const first = new Item({ name: 'A' });
    const duplicate = new Item({ name: 'A' });
    const third = new Item({ name: 'C' });
    try {
      await Item.bulkSave([first, duplicate, third]);
    } catch (error) {
      console.log('Write error:', error.code); // 11000
    }
    console.log('C exists:', !!await Item.exists({ _id: third._id }));
    console.log('C isNew:', third.isNew);
    console.log('C modified:', third.isModified());
    console.log('Save hooks:', saved);
    await Item.bulkSave([third]);
    console.log('C exists after retry:', !!await Item.exists({ _id: third._id }));
  } finally {
    await mongoose.disconnect();
  }
}
main().catch(console.error);

Before the fix: C does not exist, but isNew and isModified() are false, save hooks include C, and retry still leaves C absent.

Expected Behavior

MongoDB correctly inserts A, rejects the second A, and stops before C. Mongoose should preserve C's new/modified state and omit its success hooks. Retrying C should insert it.

The same problem affects pending updates. Correctly mapping errors requires using write-operation indexes: unchanged input documents generate no operation, and distinct document instances can share an _id.

Related work checked

#15495 concerns unordered bulkSave behavior; #14763 concerns unmatched writes/version checks; #16432/#16438 concern transaction callback retries. #16474 optimized failure lookup by ID but retains the ordered-tail problem. The open middleware/validation work in #16512 does not fix this result-handling path. I found no existing report or fix for this trigger.

A fix and five regression cases are ready, covering short/large batches, pending updates, unchanged documents, repeated IDs, mixed inserts/updates, post-save hooks, and retries.

Activity

  1. added this to the 9.10.3 milestone on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions