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

  1. Create the recorder, apply edits, and commit immediately without intervening mutations to that file's content
  2. Ensure only one UpdateRecorder per file is active at a time; merge edits into a single recorder instead
  3. Re-create the recorder after any code path rewrites the file (re-read from the fresh tree content)
  4. 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

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


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