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-negative

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Transform the operation against any concurrently applied ops before applying (TextOperation.transform)
  2. Recompute the operation against the current content so baseLength equals str.length
  3. Validate baseLength === content.length before apply and reject mismatches early
  4. 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

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


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