{"record":{"id":"11380da8dd013a8e","repo":"overleaf/overleaf","slug":"the-op-traverses-more-elements-than-the-document-h-11380d","errorCode":null,"errorMessage":"The op traverses more elements than the document has","messagePattern":"The op traverses more elements than the document has","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"services/document-updater/app/js/sharejs/types/text-tp2.js","lineNumber":384,"sourceCode":"          if (chunk.d !== undefined) {\n            throw new Error(\n              'The transformed op deletes locally inserted characters - it cannot be purged of the insert.'\n            )\n          }\n\n          if (typeof chunk === 'number') {\n            length -= chunk\n          } else {\n            append(newOp, chunk)\n          }\n        }\n      }\n    } else {\n      // Skip or delete\n      while (length > 0) {\n        chunk = take(length, true)\n        if (chunk === null) {\n          throw new Error(\n            'The op traverses more elements than the document has'\n          )\n        }\n\n        append(newOp, chunk)\n        if (!chunk.i) {\n          length -= componentLength(chunk)\n        }\n      }\n    }\n  }\n\n  // Append extras from op1\n  while ((component = take())) {\n    if (component.i === undefined) {\n      throw new Error(`Remaining fragments in the op: ${component}`)\n    }\n    append(newOp, component)","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/document-updater/app/js/sharejs/types/text-tp2.js#L366-L402","documentation":"In transformer(), when consuming skip or delete components, take() must supply document elements for the full length. If the document fragments run out before the component's length is consumed, the op traverses more of the document than exists, so the transform aborts. Like error 222, this signals the op and the document/op it is transformed against are out of sync.","triggerScenarios":"type.transform(op, otherOp, side) where op's skips/deletes exceed the document length implied by otherOp; transforming against an op from a different document version; applying the same transform twice; side-specific replay where one side already consumed the ops.","commonSituations":"Race between document-updater's update queue and a snapshot fetch; duplicate ops delivered via Redis pubsub; ops transformed against a doc that was rolled back by a history resync; combining ops from different projects/documents.","solutions":["Resync from a fresh snapshot and re-apply/transform the op against the correct history order.","Ensure ops are transformed exactly once, in order, with the correct side ('left'/'right').","Verify document identity and version before transform (version vector check).","Deduplicate incoming ops at the update-pipeline level before transforming."],"exampleFix":"// before\nupdates.forEach(u => op = type.transform(op, u.op, 'right'))\n// after\nconst known = new Set(appliedVersions)\nfor (const u of updates) {\n  if (known.has(u.version)) continue // dedupe\n  op = type.transform(op, u.op, 'right')\n}","handlingStrategy":"try-catch","validationCode":"if (opSkipDeleteLength(op) > docLengthAfter(otherOp)) {\n  return resyncFromSnapshot(op.docId)\n}","typeGuard":"function isTp2Op(op) {\n  return Array.isArray(op) && op.every(c => typeof c === 'number' && c > 0 || (typeof c === 'object' && (c.i !== undefined) !== (c.d !== undefined)))\n}","tryCatchPattern":"try {\n  transformed = tp2type.transform(op, otherOp, side)\n} catch (e) {\n  if (e.message === 'The op traverses more elements than the document has') {\n    await resyncDocument(op.docId) // refetch snapshot + replay history\n    throw new RetryableError(e)\n  }\n  throw e\n}","preventionTips":["Transform each incoming op exactly once; track applied versions.","Queue ops and apply in strict server order; never transform against a rolled-back doc.","Deduplicate pubsub-delivered updates by op id/version."],"tags":["ot","transform","stale-snapshot","document-updater"],"backgroundTag":"operation-out-of-bounds","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}