affaan-m/ECC · error · Error

Refusing to replace modified legacy Codex artifact

Error message

Refusing to replace modified legacy Codex artifact: ${filePath}

What it means

assertInstalledStateUnmodified refuses to proceed when an entry in the sync state has NO installedSha256 (i.e. the artifact was recorded as previously absent) but the file now EXISTS on disk. Since the installer cannot verify the on-disk content matches what it wrote (there is no baseline digest), any existing file could be user-modified, so it aborts rather than overwriting user data.

Solutions

  1. Compare the on-disk file with the intended artifact; if the content is yours and unwanted, delete or move it, then rerun the install.
  2. Regenerate the sync state (delete statePath and reinstall) so the entry reflects the file's current hash instead of the 'absent' baseline.
  3. If the file is legitimately the artifact (e.g. restored from the plugin), verify its sha256 matches the shipped artifact and update the state entry's installedSha256.
  4. Back up the existing file before any deletion so user customizations are not lost.

Example fix

// before: file exists but state has no installedSha256
mv ~/.codex/AGENTS.md ~/.codex/AGENTS.md.user-backup
// after: path is clear; rerun install, then merge any custom edits back manually
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
function artifactAbsentEntryConflict(state, filePath) {
  const entry = state.paths.find(e => path.resolve(e.path) === path.resolve(filePath));
  return entry && !entry.installedSha256 && fs.existsSync(filePath);
}

Try / catch

try {
  beginLegacySyncState(options);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Refusing to replace modified legacy Codex artifact')) {
    const p = err.message.split('artifact: ')[1];
    console.error(`Back up and remove your local copy of ${p}, then rerun install`);
    process.exitCode = 1;
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: beginLegacySyncState -> assertInstalledStateUnmodified over a state entry without installedSha256 while readRegularFileNoFollow(filePath) returns a snapshot — i.e. the file was deleted at install time (recorded as absent) and has since been created by anything else (user, another tool, a later partial install).

Common situations: User restored a managed file from a backup after uninstalling; another plugin or dotfiles manager recreated the file; a failed previous install left the file behind without updating state; git checkout/branch switch restored the file.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/2a6e1cd684bd4bbe. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/codex-legacy-sync.js:212

  return {
    path: filePath,
    installedSha256: null,
    previousType,
    previousContentBase64: snapshot ? snapshot.content.toString('base64') : null,
    previousMode: snapshot ? snapshot.mode : null,
  };
}

function assertInstalledStateUnmodified(state) {
  for (const entry of state.paths) {
    const filePath = path.resolve(entry.path);
    const trustedRoot = getTrustedRoot(state, filePath);
    if (!trustedRoot || hasUnsafeManagedAncestor(filePath, trustedRoot)) {
      throw new Error(`Refusing to reuse unsafe legacy Codex ownership path: ${filePath}`);
    }
    const snapshot = readRegularFileNoFollow(filePath);
    if (!entry.installedSha256) {
      if (snapshot) throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);
      continue;
    }
    const digest = snapshot
      ? crypto.createHash('sha256').update(snapshot.content).digest('hex')
      : null;
    if (digest !== entry.installedSha256) {
      throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);
    }
  }
}

function beginLegacySyncState(options) {
  const codexHome = path.resolve(options.codexHome);
  const statePath = getStatePath(codexHome);
  const configPath = path.join(codexHome, 'config.toml');
  const agentsPath = path.join(codexHome, 'AGENTS.md');
  const installedHooksPath = options.installedHooksPath ? path.resolve(options.installedHooksPath) : null;
  const priorState = readStateIfPresent(statePath);

View on GitHub (pinned to 8321021c54)