{"record":{"id":"86c2c1430a359649","repo":"Automattic/mongoose","slug":"if-thenexpr-or-elseexpr-is-string-it-must-be-eith","errorCode":null,"errorMessage":"If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP","messagePattern":"If thenExpr or elseExpr is string, it must be either \\$\\$DESCEND, \\$\\$PRUNE or \\$\\$KEEP","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"warning","filePath":"lib/aggregate.js","lineNumber":769,"sourceCode":" *       }\n *     });\n *\n *     // $redact often comes with $cond operator, you can also use the following syntax provided by mongoose\n *     await Model.aggregate(pipeline).redact({ $eq: [ '$level', 5 ] }, '$$PRUNE', '$$DESCEND');\n *\n * @param {object} expression redact options or conditional expression\n * @param {string|object} [thenExpr] true case for the condition\n * @param {string|object} [elseExpr] false case for the condition\n * @return {Aggregate} this\n * @see $redact https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/\n * @api public\n */\n\nAggregate.prototype.redact = function(expression, thenExpr, elseExpr) {\n  if (arguments.length === 3) {\n    if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||\n      (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {\n      throw new MongooseError('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');\n    }\n\n    expression = {\n      $cond: {\n        if: expression,\n        then: thenExpr,\n        else: elseExpr\n      }\n    };\n  } else if (arguments.length !== 1) {\n    throw new TypeError('Invalid arguments');\n  }\n\n  return this.append({ $redact: expression });\n};\n\n/**\n * Execute the aggregation with explain","sourceCodeStart":751,"sourceCodeEnd":787,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/aggregate.js#L751-L787","documentation":"During index creation (gh-15056 fix), Mongoose compares each unnamed index spec against specs it has already seen using isIndexSpecEqual(). Two definitions covering the same keys with the same direction (e.g. { email: 1 } declared twice) usually mean the field was marked index: true and also declared with schema.index({ email: 1 }). The duplicate is not harmful to correctness but creates redundant createIndex attempts and log noise, so Mongoose warns and asks you to remove one.","triggerScenarios":"new Schema({ email: { type: String, index: true } }) combined with schema.index({ email: 1 }); compound indexes where one field also has index: true (e.g. { email: 1 } from index:true and schema.index({ email: 1, name: -1 }) — note same-key single-field duplicates are what triggers the exact-match warning); copied index blocks that declare the same spec twice; both unique: true on the field and a schema.index with the same keys.","commonSituations":"Growing schemas where a field first got index: true and later a compound index was added; merging PRs where two devs index the same field; importing index definitions from db.collection.getIndexes() output that already includes field-level indexes; performance cleanups after syncIndexes() reports duplicate work.","solutions":["Remove the field-level index: true when a schema.index() already covers that field, or vice versa — keep exactly one declaration.","If both are needed conceptually (single-field plus different compound), give them distinct names so they are not 'duplicate unnamed' specs, and verify the compound actually differs in keys/direction.","Run model.syncIndexes() and inspect the resulting list with db.collection.getIndexes() to confirm each intended index exists once.","Codemod your schemas to a single style (prefer schema.index()) to prevent future duplicates."],"exampleFix":"// before\nconst schema = new Schema({ email: { type: String, index: true } });\nschema.index({ email: 1 }); // duplicate\n\n// after\nconst schema = new Schema({ email: String });\nschema.index({ email: 1 });","handlingStrategy":"validation","validationCode":"const specKey = (fields) => JSON.stringify(Object.keys(fields).sort().map(k => [k, fields[k]]));\nfunction assertNoDuplicateIndexes(schema) {\n  const seen = new Set();\n  for (const [fields, options] of schema.indexes()) {\n    if (options.name == null) {\n      const key = specKey(fields);\n      if (seen.has(key)) throw new Error(`Duplicate index on ${key}; keep either index:true or schema.index()`);\n      seen.add(key);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick one indexing style per codebase (prefer schema.index()) and lint against index: true.","After adding a compound index, remove now-redundant single-field index: true on its prefix fields when the single index is unnecessary.","Run model.syncIndexes() in staging and diff getIndexes() output before shipping schema changes."],"tags":["mongoose","indexes","schema","duplicate","warning"],"backgroundTag":"duplicate-index-definition","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}