angular/angular-cli · error · ContentHasMutatedException

Content at path "${path}" has changed between the start and

Error message

Content at path "${path}" has changed between the start and the end of an update.

What it means

Thrown by HostTree.commitUpdate when the file targeted by the UpdateRecorder can no longer be found in the tree — the content/path mutated between beginUpdate and commitUpdate. This guards against applying text edits computed against stale content, which would silently corrupt the file.

Source

Thrown at packages/angular_devkit/schematics/src/tree/host-tree.ts:401

      throw new FileDoesNotExistException(p);
    }
    const c = typeof content == 'string' ? Buffer.from(content) : content;
    this._record.overwrite(p, c as {} as virtualFs.FileBuffer).subscribe();
  }
  beginUpdate(path: string): UpdateRecorder {
    const entry = this.get(path);
    if (!entry) {
      throw new FileDoesNotExistException(path);
    }

    return UpdateRecorderBase.createFromFileEntry(entry);
  }
  commitUpdate(record: UpdateRecorder): void {
    if (record instanceof UpdateRecorderBase) {
      const path = record.path;
      const entry = this.get(path);
      if (!entry) {
        throw new ContentHasMutatedException(path);
      } else {
        const newContent = record.apply(entry.content);
        if (!newContent.equals(entry.content)) {
          this.overwrite(path, newContent);
        }
      }
    } else {
      throw new InvalidUpdateRecordException();
    }
  }

  // Structural methods.
  create(path: string, content: Buffer | string): void {
    const c = typeof content == 'string' ? Buffer.from(content) : content;
    this._record.create(this._normalizePath(path), c as {} as virtualFs.FileBuffer).subscribe();
  }
  delete(path: string): void {
    this._recordSync.delete(this._normalizePath(path));

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Complete the update (commitUpdate) before any other tree mutation of the same path.
  2. Don't delete/rename/recreate the file while holding an open UpdateRecorder.
  3. Recreate the recorder after intermediate mutations: beginUpdate again after changes, then edit and commit.
  4. If using applyChanges-style flows, ensure each commitUpdate immediately follows its beginUpdate.

Example fix

// before
const rec = tree.beginUpdate(p);
tree.delete(p);
tree.create(p, other);
tree.commitUpdate(rec);
// after
const rec = tree.beginUpdate(p);
// ...edits only...
tree.commitUpdate(rec);
tree.delete(p);
tree.create(p, mergedContent);
Defensive patterns

Strategy: try-catch

Validate before calling

const before = tree.get(path)?.content;
// ...after edits, before commit:
const unchanged = tree.get(path)?.content?.equals(before!);

Try / catch

try {
  tree.commitUpdate(record);
} catch (e) {
  if ((e as Error).message.includes('Content has mutated')) {
    // restart: re-beginUpdate on current content and reapply edits
  } else throw e;
}

Prevention

When it happens

Trigger: Deleting, renaming, or re-creating the file (or overwriting it and having commitUpdate re-read different content) between tree.beginUpdate(path) and tree.commitUpdate(record); running nested rules that mutate the same file inside an update session; using a recorder whose entry was captured before a merge/branch operation.

Common situations: Schematics that delete-and-recreate a file while an update recorder is open; concurrent rules mutating the same file; a rule that commits other changes mid-update.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/cdb5bae664bad1a0. Report an issue: GitHub.