angular/angular-cli · error · ContentHasMutatedException
Content at path "${this.path}" has changed between the start
Error message
Content at path "${this.path}" has changed between the start and the end of an update. What it means
The recorder buffers staged changes against a snapshot of the file's content (this.data). On apply(), if the underlying content passed in no longer equals that snapshot, the file mutated mid-update (ContentHasMutatedException). Schematics requires a stable buffer so the recorded diff applies to exactly the bytes it was created from.
Source
Thrown at packages/angular_devkit/schematics/src/tree/recorder.ts:96
}
insertRight(index: number, content: Buffer | string): UpdateRecorder {
this._assertIndex(index);
this.content.appendRight(index, content.toString());
return this;
}
remove(index: number, length: number): UpdateRecorder {
this._assertIndex(index);
this.content.remove(index, index + length);
return this;
}
apply(content: Buffer): Buffer {
if (!content.equals(this.data)) {
throw new ContentHasMutatedException(this.path);
}
// Schematics only support writing UTF-8 text
const result = Buffer.from((this.bom ? '\uFEFF' : '') + this.content.toString(), 'utf-8');
return result;
}
}
View on GitHub (pinned to bb72145f9a)
Solutions
- Create the recorder, apply edits, and commit immediately without intervening mutations to that file's content
- Ensure only one UpdateRecorder per file is active at a time; merge edits into a single recorder instead
- Re-create the recorder after any code path rewrites the file (re-read from the fresh tree content)
- If you must transform content, do it before beginUpdate or via tree.overwrite with the fully transformed buffer
Example fix
// before
const recorder = tree.beginUpdate('src/app.ts');
someOtherRuleThatRewritesAppTs(tree); // mutates content mid-update
recorder.insertLeft(0, '// hi');
tree.commitUpdate(recorder); // throws ContentHasMutatedException
// after
const recorder = tree.beginUpdate('src/app.ts');
recorder.insertLeft(0, '// hi');
tree.commitUpdate(recorder);
someOtherRuleThatRewritesAppTs(tree); // run after commit Defensive patterns
Strategy: validation
Validate before calling
const recorder = tree.beginUpdate(path);
// ...edits...
// before commit, ensure content unchanged:
const current = tree.read(path);
if (!current || !current.equals(initialSnapshot)) throw new Error('File changed during update');
tree.commitUpdate(recorder); Try / catch
try {
tree.commitUpdate(recorder);
} catch (e) {
if (/has changed between the start and the end/.test(String(e))) {
// re-open a fresh recorder against current content and retry edits
} else throw e;
} Prevention
- Open and commit each recorder in one tight, synchronous block
- Never hold two recorders for the same file at once
- Do not mutate file buffers between beginUpdate and commitUpdate
- Re-read the tree after any external process may have rewritten files
When it happens
Trigger: Calling tree.apply() with a Buffer that differs from what the UpdateRecorderBase captured at creation; mutating the file buffer (or the underlying host file) between creating the recorder from tree.beginUpdate(path) and calling recorder.apply()/commit.
Common situations: Two recorders opened on the same file where one commit rewrites the bytes; a rule or host transform rewrites the file during the same tree pass; async code mutating the buffer before commit; tooling (e.g. a format step) touching the file between beginUpdate and commit.
Related errors
- Option "project" is required.
- Project is not defined in this workspace.
- Targets are not defined for this project.
- Failed to decode "${path}" as ${encoding} text.
- Index ${index} outside of range [0, ${this.content.original.
AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30).
Data as JSON: /api/errors/b7b619e5c91e4fc3.
Report an issue: GitHub.