hcengineering/platform · error

No valid diff possible applying ${op.path} ${JSON.stringify(

Error message

No valid diff possible applying ${op.path} ${JSON.stringify(error, undefined, 2)}

What it means

recreateChangeContentSteps replays an editor diff by applying ProseMirror operations one at a time; when an operation throws, it shifts to the next queued op and retries, but if the op queue is exhausted it throws this error including the failing op path and the underlying error JSON. It means the recorded change set cannot be validly applied to the document (documents diverged beyond what the diff can bridge).

Source

Thrown at plugins/text-editor-resources/src/components/diff/recreate.ts:196

      const afterStepJSON = clone(this.currentDoc) // working document receiving patches
      const pathParts = op.path.split('/')

      // collect operations until we receive a valid document:
      // apply ops-patches until a valid prosemirror document is retrieved,
      // then try to create a transformation step or retry with next operation
      while (toDoc == null) {
        applyPatch(afterStepJSON, [op])

        try {
          toDoc = this.schema.nodeFromJSON(afterStepJSON)
          toDoc.check()
        } catch (error: any) {
          toDoc = null
          if (this.ops.length > 0) {
            op = this.ops.shift() as Operation
            ops.push(op)
          } else {
            throw new Error(`No valid diff possible applying ${op.path} ${JSON.stringify(error, undefined, 2)}`)
          }
        }
      }

      // apply operation (ignoring afterStepJSON)
      if (ops.length === 1 && (pathParts.includes('attrs') || pathParts.includes('type'))) {
        // Node markup is changing
        this.addSetNodeMarkup() // a lost update is ignored
        ops = []
      } else if (ops.length === 1 && op.op === 'replace' && pathParts[pathParts.length - 1] === 'text') {
        // Text is being replaced, we apply text diffing to find the smallest possible diffs.
        this.addReplaceTextSteps(op, afterStepJSON)
        ops = []
      } else if (this.addReplaceStep(toDoc, afterStepJSON)) {
        // operations have been applied
        ops = []
      }
    }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Log op.path and the wrapped error to identify the first path that no longer matches the document schema
  2. Recreate the diff between the actual current doc and target doc instead of replaying the stale one
  3. Update the editor schema/migrations so legacy node types and attrs referenced by the ops still resolve
  4. Guard the call site (init) with try-catch and fall back to re-rendering the document rather than crashing the editor

Example fix

// before
try {
  await recreateChangeContentSteps(doc, change)
} catch (e) { throw e }
// after
try {
  await recreateChangeContentSteps(doc, change)
} catch (e) {
  console.error('Diff replay failed, rebuilding diff from live doc', e)
  await rebuildDiffFromDocs(currentDoc, targetDoc)
}
Defensive patterns

Strategy: try-catch

Validate before calling

function opPathsExistInSchema (ops: Operation[], schema: Schema): boolean {
  return ops.every((op) => {
    const parts = op.path.split('.')
    return parts.every((p) => p in schema.nodes || p in schema.marks || ['attrs', 'type', 'content'].includes(p))
  })
}
// if (!opPathsExistInSchema(change.ops, editor.schema)) rebuildDiff()

Type guard

function isApplicableOp (op: Operation, doc: ProsemirrorNode): boolean {
  const from = (op as any).from
  return from === undefined || (typeof from === 'number' && from <= doc.content.size)
}

Try / catch

try {
  recreateChangeContentSteps.call(recreator)
} catch (err) {
  if (err instanceof Error && err.message.startsWith('No valid diff possible')) {
    console.error('Diff replay diverged:', err.message)
    fallbackToFullRedraw(recreator.targetDoc)
    return
  }
  throw err
}

Prevention

When it happens

Trigger: Applying a stored diff/recreated change to a doc whose structure no longer matches (schema change, doc edited since the diff was captured); all ops fail and this.ops is empty so no further retry is possible.

Common situations: Collaborative documents modified concurrently while a diff was being replayed; editor schema upgraded so op paths (attrs/type) no longer exist; corrupted or hand-edited change records.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/edcad11743d4ffd0. Report an issue: GitHub.