affaan-m/ECC · error · Error

Refusing to record a legacy sync path outside trusted roots:

Error message

Refusing to record a legacy sync path outside trusted roots: ${filePath}

What it means

recordLegacySyncPath() adds a file to the sync-state's managed set. It resolves the path and asks getTrustedRoot() for a root (codexHome, recorded trustedRoots, or installedHooksPath) that contains it; with no match it refuses, so the state file can never record paths outside the directories ECC was told it owns.

Source

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

    paths: [],
    rollbackPaths: [],
  };

  for (const [key, filePath] of [['config', configPath], ['agents', agentsPath]]) {
    if (priorState) break;
    const snapshot = readRegularFileNoFollow(filePath, 'utf8');
    state.before[key] = snapshot ? snapshot.content : null;
  }
  atomicWriteJson(statePath, state);
  return statePath;
}

function recordLegacySyncPath(options) {
  const state = readState(options.statePath);
  const filePath = path.resolve(options.filePath);
  const trustedRoot = getTrustedRoot(state, filePath);
  if (!trustedRoot) {
    throw new Error(`Refusing to record a legacy sync path outside trusted roots: ${filePath}`);
  }
  if (hasUnsafeManagedAncestor(filePath, trustedRoot)) {
    throw new Error(`Refusing to manage legacy sync path through symlinked ancestor: ${filePath}`);
  }
  if (!state.paths.some(entry => entry.path === filePath)) {
    const snapshot = snapshotLegacyPath(filePath);
    state.paths.push(snapshot);
    state.rollbackPaths = [...(state.rollbackPaths || []), { ...snapshot }];
    atomicWriteJson(options.statePath, state);
  }
}

function rollbackLegacyCodexSync(options) {
  const state = readState(options.statePath);
  const restoredPaths = [];
  const retainedPaths = [];

  const rollbackPaths = Array.isArray(state.rollbackPaths) ? state.rollbackPaths : state.paths;

View on GitHub (pinned to 06c5e118c4)

Solutions

  1. Derive the path from the same codexHome passed to beginLegacySyncState(), e.g. path.join(codexHome, 'config.toml')
  2. If the path legitimately lives elsewhere, pass it via installedHooksPath (or trustedRoots) at begin time so it becomes trusted
  3. Fix the caller to always pass absolute paths built under the managed roots

Example fix

// before: recordLegacySyncPath({ statePath, filePath: 'config.toml' }) // resolves against cwd -> refused
recordLegacySyncPath({ statePath, filePath: path.join(codexHome, 'config.toml') });
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function isUnderTrustedRoot(filePath, trustedRoots) {
  const abs = path.resolve(filePath);
  return trustedRoots.some(r => abs === r || abs.startsWith(path.resolve(r) + path.sep));
}
if (!isUnderTrustedRoot(filePath, trustedRoots)) throw new Error('caller bug: path not under a trusted root: ' + filePath);

Prevention

When it happens

Trigger: Calling recordLegacySyncPath({ statePath, filePath }) where path.resolve(filePath) lands outside codexHome and the hooks path — commonly a relative path that resolved against process.cwd(), or a codexHome option that differs from the one used at beginLegacySyncState() time.

Common situations: Caller computes the managed path from the repo root instead of codexHome; options objects built in two places with different codexHome values; wrong variable passed as filePath.

Related errors


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/4081be85c9d64612. Report an issue: GitHub.