overleaf/overleaf · error · ApplyError
The operation didn't operate on the whole string.
Error message
The operation didn't operate on the whole string.
What it means
After applying all ops, apply() verifies the input cursor consumed exactly the whole string. If retains+deletes did not cover every character, some text would be silently dropped, so the library throws ApplyError instead. This catches operations whose baseLength is smaller than the document.
Source
Thrown at libraries/overleaf-editor-core/lib/operation/text_operation.js:326
}
result += str.slice(inputCursor, inputCursor + op.length)
inputCursor += op.length
} else if (op instanceof InsertOp) {
file.comments.applyInsert(
new Range(result.length, op.insertion.length),
{ commentIds: op.commentIds }
)
result += op.insertion
} else if (op instanceof RemoveOp) {
file.comments.applyDelete(new Range(result.length, op.length))
inputCursor += op.length
} else {
throw new UnprocessableError('Unknown ScanOp type during apply')
}
}
if (inputCursor !== str.length) {
throw new TextOperation.ApplyError(
"The operation didn't operate on the whole string.",
operation,
str
)
}
if (result.length > TextOperation.MAX_STRING_LENGTH) {
throw new TextOperation.TooLongError(operation, result.length)
}
file.trackedChanges.applyTextOperation(this)
file.content = result
}
/**
* @inheritdoc
* @param {number} length of the original string; non-negativeView on GitHub (pinned to 28ad3b03b7)
Solutions
- Transform the operation against any concurrently applied ops before applying (TextOperation.transform)
- Recompute the operation against the current content so baseLength equals str.length
- Validate baseLength === content.length before apply and reject mismatches early
- Restore file/order of ops so the original op applies to the exact document version it was computed against
Example fix
// before file.apply(opBuiltForShorterDoc) // leaves trailing text unconsumed // after const [opA] = TextOperation.transform(opBuiltForShorterDoc, concurrentOp) file.apply(opA)
Defensive patterns
Strategy: validation
Validate before calling
function fullyConsumes(file, op) {
const covered = op.ops.reduce((n, o) => (o instanceof RetainOp || o instanceof RemoveOp) ? n + o.length : n, 0)
return covered === file.getContent().length
} Try / catch
try {
file.apply(op)
} catch (err) {
if (err.name === 'ApplyError' && err.message.includes("didn't operate on the whole string")) {
logger.warn({ baseLength: op.baseLength }, 'op does not cover whole string')
const [transformed] = TextOperation.transform(op, concurrentOps)
file.apply(transformed)
} else { throw err }
} Prevention
- Transform ops against all concurrent operations before applying
- Verify baseLength equals current content length before apply
- Never build ops from truncated or partial document snapshots
When it happens
Trigger: file.apply(op) where op.baseLength < str.length — e.g. an operation computed against a shorter/older document, an op missing trailing retains, or applying an op to a file that grew (e.g. a concurrent insert landed first).
Common situations: Concurrent editing where a peer's insertion wasn't transformed into this op; ops serialized without trailing retains trimmed correctly by another tool; applying an op built for a truncated preview of the document.
Related errors
- The operation's base length must be equal to the string's le
- Operation can't retain more chars than are left in the strin
- Unknown ScanOp type during apply
- resulting string would be too long
- batchedUpdate is already running
AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03).
Data as JSON: /api/errors/cd8ccdcbb6b6b0b3.
Report an issue: GitHub.