overleaf/overleaf · critical · Error

Snapshot is invalid

Error message

Snapshot is invalid

What it means

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.

Source

Thrown at services/document-updater/app/js/sharejs/types/text-tp2.js:177

  const { data } = doc
  if (data.length === 0) {
    data.push(p)
  } else if (typeof data[data.length - 1] === typeof p) {
    data[data.length - 1] += p
  } else {
    data.push(p)
  }
}

// Apply the op to the document. The document is not modified in the process.
type.apply = function (doc, op) {
  if (
    doc.totalLength === undefined ||
    doc.charLength === undefined ||
    doc.data.length === undefined
  ) {
    throw new Error('Snapshot is invalid')
  }

  checkOp(op)

  const newDoc = type.create()
  const position = { index: 0, offset: 0 }

  for (const component of Array.from(op)) {
    let part, remainder
    if (typeof component === 'number') {
      remainder = component
      while (remainder > 0) {
        part = takeDoc(doc, position, remainder)

        appendDoc(newDoc, part)
        remainder -= part.length || part
      }
    } else if (component.i !== undefined) {

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Ensure the document was created with type.create() / the 'text-tp2' type before applying ops.
  2. Migrate old snapshots: convert plain-text snapshots to tp2 format (via ShareJS upgrade/migration tooling) before applying tp2 ops.
  3. Check the document's declared OT type matches the op type ('text-tp2') in web-api/docstore configuration.
  4. Validate snapshot shape before apply: presence of totalLength, charLength, and Array.isArray(doc.data).

Example fix

// before
updater.applyUpdate(docId, op) // doc snapshot may be legacy string
// after
const doc = getSnapshot(docId)
if (typeof doc === 'string' || doc.data === undefined) {
  migrateSnapshotToTp2(docId)
}
updater.applyUpdate(docId, op)
Defensive patterns

Strategy: type-guard

Validate before calling

if (!doc || doc.totalLength === undefined || doc.charLength === undefined || !Array.isArray(doc.data)) {
  return migrateOrCreateTp2Doc(docId)
}

Type guard

function isTp2Doc(doc) {
  return (
    doc !== null && typeof doc === 'object' &&
    typeof doc.totalLength === 'number' &&
    typeof doc.charLength === 'number' &&
    Array.isArray(doc.data)
  )
}

Try / catch

try {
  tp2type.apply(doc, op)
} catch (e) {
  if (e.message === 'Snapshot is invalid') {
    const fresh = await loadTp2Snapshot(docId) // creates/migrates as needed
    return tp2type.apply(fresh, op)
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/d034887903bc3b9f. Report an issue: GitHub.