{"record":{"id":"f68530b83ff18575","repo":"overleaf/overleaf","slug":"failed-to-unarchive-doc","errorCode":null,"errorMessage":"failed to unarchive doc","messagePattern":"failed to unarchive doc","errorType":"error_code","errorClass":"DocRevValueError","httpStatus":409,"severity":"error","filePath":"services/docstore/app/js/MongoManager.js","lineNumber":233,"sourceCode":"    _id: new ObjectId(docId),\n    project_id: new ObjectId(projectId),\n    rev: archivedDoc.rev,\n  }\n  const update = {\n    $set: {\n      lines: archivedDoc.lines,\n      ranges: archivedDoc.ranges || {},\n    },\n    $unset: {\n      inS3: true,\n    },\n  }\n  const pipeline = convertUpdateToPipeline(update)\n  const payloadSize = BSON.calculateObjectSize(pipeline)\n  Metrics.count('mongo_docs_write', payloadSize, 1, { method: 'restore' })\n  const result = await db.docs.updateOne(query, pipeline)\n  if (result.matchedCount !== 1) {\n    throw new Errors.DocRevValueError('failed to unarchive doc', {\n      docId,\n      rev: archivedDoc.rev,\n    })\n  }\n}\n\nasync function getDocRev(docId) {\n  const doc = await db.docs.findOne(\n    { _id: new ObjectId(docId.toString()) },\n    { projection: { rev: 1 } }\n  )\n  return doc && doc.rev\n}\n\n/**\n * Helper method  to support optimistic locking.\n *\n * Check that the rev of an existing doc is unchanged. If the rev has","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/docstore/app/js/MongoManager.js#L215-L251","documentation":"restoreArchivedDoc calls db.docs.updateOne with a pipeline built from the stored update and expects exactly one document to match the query. If matchedCount !== 1 (0 matches), the archived doc is not present under the given query (docId/rev), so the unarchive silently failed. The library throws DocRevValueError to surface this as a failed restore rather than returning a stale result.","triggerScenarios":"Calling MongoManager.restoreArchivedDoc (e.g. via the docstore unarchive HTTP endpoint) when the doc no longer exists in the archived collection under the query, or the archived doc's rev changed between read and update, so updateOne matches 0 documents.","commonSituations":"Race where another process unarchives or deletes the archived doc between the fetch and the updateOne; stale docId passed by a caller; retry of an already-completed unarchive; replica-set failover causing a lost update window.","solutions":["Verify the doc still exists in the docs collection with the expected archived state/rev before calling restoreArchivedDoc.","Handle the race by retrying: re-fetch the archived doc and call restore again if another process changed it.","Check the docId is correct and the doc was actually archived (not deleted) before restoring.","Catch Errors.DocRevValueError in the caller and return a 404/409 to the HTTP client instead of a 500."],"exampleFix":"// before\nawait docstoreManager.unArchiveDoc(projectId, docId)\n// after\ntry {\n  await docstoreManager.unArchiveDoc(projectId, docId)\n} catch (err) {\n  if (err instanceof Errors.DocRevValueError) {\n    return res.status(404).json({ error: 'archived doc not found or already restored' })\n  }\n  throw err\n}","handlingStrategy":"try-catch","validationCode":"const archived = await db.docs.findOne({ _id: docId })\nif (!archived || archived.rev !== expectedRev) {\n  throw new Error(`archived doc ${docId} not present with rev ${expectedRev}`)\n}","typeGuard":"function isRestoreable(doc) {\n  return doc != null && typeof doc.rev === 'number' && !Number.isNaN(doc.rev)\n}","tryCatchPattern":"try {\n  await MongoManager.restoreArchivedDoc(docId, rev)\n} catch (err) {\n  if (err instanceof Errors.DocRevValueError) {\n    // doc already restored or gone — treat as 404/409, do not retry blindly\n    return { status: 404 }\n  }\n  throw err\n}","preventionTips":["Check the archived doc's existence and rev immediately before restoring.","Make unarchive operations idempotent so races between processes are harmless.","Return 404/409 to clients rather than letting DocRevValueError bubble up as a 500.","Log docId and rev on failure to speed up race diagnosis."],"tags":["mongodb","docstore","concurrency","unarchive"],"backgroundTag":"mongo-update-matched-zero-docs","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}