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
- Complete the update (commitUpdate) before any other tree mutation of the same path.
- Don't delete/rename/recreate the file while holding an open UpdateRecorder.
- Recreate the recorder after intermediate mutations: beginUpdate again after changes, then edit and commit.
- 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
- Never delete/rename/recreate a file between beginUpdate and commitUpdate
- Commit the recorder immediately after edits, before other tree mutations
- Avoid nested rules that touch the same file inside an open update
- Recreate the recorder from fresh content after any intermediate change
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
- Invalid record instance.
- schematicName cannot be undefined.
- The "not" keyword is not supported in JSON Schema.
- Could not find (/.angular.json)
- Unknown schematics built-in module '${id}' requested from sc
AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30).
Data as JSON: /api/errors/cdb5bae664bad1a0.
Report an issue: GitHub.