{"record":{"id":"d034887903bc3b9f","repo":"overleaf/overleaf","slug":"snapshot-is-invalid","errorCode":null,"errorMessage":"Snapshot is invalid","messagePattern":"Snapshot is invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"services/document-updater/app/js/sharejs/types/text-tp2.js","lineNumber":177,"sourceCode":"\n  const { data } = doc\n  if (data.length === 0) {\n    data.push(p)\n  } else if (typeof data[data.length - 1] === typeof p) {\n    data[data.length - 1] += p\n  } else {\n    data.push(p)\n  }\n}\n\n// Apply the op to the document. The document is not modified in the process.\ntype.apply = function (doc, op) {\n  if (\n    doc.totalLength === undefined ||\n    doc.charLength === undefined ||\n    doc.data.length === undefined\n  ) {\n    throw new Error('Snapshot is invalid')\n  }\n\n  checkOp(op)\n\n  const newDoc = type.create()\n  const position = { index: 0, offset: 0 }\n\n  for (const component of Array.from(op)) {\n    let part, remainder\n    if (typeof component === 'number') {\n      remainder = component\n      while (remainder > 0) {\n        part = takeDoc(doc, position, remainder)\n\n        appendDoc(newDoc, part)\n        remainder -= part.length || part\n      }\n    } else if (component.i !== undefined) {","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/document-updater/app/js/sharejs/types/text-tp2.js#L159-L195","documentation":"type.apply checks that the snapshot has the three fields tp2 documents carry: totalLength, charLength, and data (an array). If any is undefined, the snapshot is not a valid text-tp2 document - likely a plain string, a null/empty object, or a snapshot of a different OT type. tp2 tracks deleted characters in data, so its invariants differ from other types.","triggerScenarios":"Calling type.apply(doc, op) with doc = '' or {} or a plain string; applying a tp2 op to a snapshot created by the 'text' type (which is a string) instead of 'text-tp2'; a deserialized snapshot that lost its fields; doc.create never called before apply.","commonSituations":"Changing a project's OT type from text to text-tp2 while old snapshots persist; loading snapshots from a store where only the string body was saved; mixing types in document-updater config (doc type mismatch with the op being applied).","solutions":["Ensure the document was created with type.create() / the 'text-tp2' type before applying ops.","Migrate old snapshots: convert plain-text snapshots to tp2 format (via ShareJS upgrade/migration tooling) before applying tp2 ops.","Check the document's declared OT type matches the op type ('text-tp2') in web-api/docstore configuration.","Validate snapshot shape before apply: presence of totalLength, charLength, and Array.isArray(doc.data)."],"exampleFix":"// before\nupdater.applyUpdate(docId, op) // doc snapshot may be legacy string\n// after\nconst doc = getSnapshot(docId)\nif (typeof doc === 'string' || doc.data === undefined) {\n  migrateSnapshotToTp2(docId)\n}\nupdater.applyUpdate(docId, op)","handlingStrategy":"type-guard","validationCode":"if (!doc || doc.totalLength === undefined || doc.charLength === undefined || !Array.isArray(doc.data)) {\n  return migrateOrCreateTp2Doc(docId)\n}","typeGuard":"function isTp2Doc(doc) {\n  return (\n    doc !== null && typeof doc === 'object' &&\n    typeof doc.totalLength === 'number' &&\n    typeof doc.charLength === 'number' &&\n    Array.isArray(doc.data)\n  )\n}","tryCatchPattern":"try {\n  tp2type.apply(doc, op)\n} catch (e) {\n  if (e.message === 'Snapshot is invalid') {\n    const fresh = await loadTp2Snapshot(docId) // creates/migrates as needed\n    return tp2type.apply(fresh, op)\n  }\n  throw e\n}","preventionTips":["Always create documents with the 'text-tp2' type before applying ops.","Verify persisted snapshots retain the full tp2 object, not just the string body.","Add a migration step for legacy 'text' snapshots when switching OT types."],"tags":["ot","snapshot-corruption","type-mismatch","document-updater"],"backgroundTag":"invalid-snapshot-format","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}