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
- 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).
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
- 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.
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
- Referenced element not a string
- sharejs-text-ot
- Deleted string does not match
- invalid / missing instruction in op
- must be a string?
AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03).
Data as JSON: /api/errors/d034887903bc3b9f.
Report an issue: GitHub.