santifer/career-ops · critical · Error

Safety violation (${violation.message}) and revert also fail

Error message

Safety violation (${violation.message}) and revert also failed (${revertErr.message})

What it means

Thrown when apply()'s safety validation ran successfully AND detected a real violation — one or more user-layer paths (USER_PATHS, e.g. cv.md, data/*, reports/*) were modified by the update — and then the scoped revert of ONLY the system-layer paths also failed. This is distinct from error 361 (where validation itself could not run). Here validation confirmed a breach; the code deliberately does NOT git-checkout the violated user paths back to HEAD (that would clobber the user's local content), only reverts system paths. When that system-path revert fails, the safety violation is chained via `cause` so both the breach list and the revert failure surface together.

Source

Thrown at update-system.mjs:1313

      throw err;
    }

    if (violatedUserPaths.size > 0) {
      console.error('Aborting: user files were touched. Rolling back system files...');
      // Revert ONLY the system-layer updates — never `git checkout` the
      // violated user paths back to HEAD. Doing so would overwrite the
      // user's working-tree content (accumulated STAR+R stories, local
      // edits) with whatever is committed upstream, causing data loss.
      // The user files were flagged as touched by the update, not by the
      // user; leaving them as-is is the safe choice — the user decides
      // what to do with them.
      const violation = new Error('Update aborted: user files were touched.');
      try {
        revertPaths([...updated], initialStatusPaths);
      } catch (revertErr) {
        // If the revert itself fails, don't lose the safety-violation
        // diagnostic — chain it via `cause` so the user sees both.
        throw new Error(
          `Safety violation (${violation.message}) and revert also failed (${revertErr.message})`,
          { cause: violation },
        );
      }
      console.error(`User file(s) left as-is (your content was NOT overwritten):`);
      for (const f of violatedUserPaths) console.error(`  ${f}`);
      // `throw` (not `process.exit`) so the outer `finally` runs and
      // .update-lock is removed. Exiting here would leak the lock and
      // permanently block subsequent updates until the user deletes
      // it manually.
      throw violation;
    }

    // 5. Install any new dependencies
    try {
      execSync('npm install --silent', { cwd: ROOT, timeout: NPM_INSTALL_TIMEOUT_MS });
    } catch {
      console.log('npm install skipped (may need manual run)');

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Read the full message and err.cause: it names the violated user files (already logged to stderr as 'SAFETY VIOLATION') and the revert error — back up those user files immediately since they hold your real content mixed with upstream edits.
  2. Fix the revert failure first (see its message): typically `rm -f .git/index.lock` or free disk, then `git checkout -- <each system path>` manually to finish the rollback the updater couldn't complete.
  3. Restore the violated user files from your last good state: `git checkout HEAD -- <user file>` only if HEAD is your own commit, otherwise recover from a backup — do NOT blindly reset, as upstream touched them.
  4. Report the upstream packaging bug: a USER_PATHS file was modified in the system branch, which is exactly what the invariant forbids. Pin to a known-good update ref until fixed.
  5. After the tree is clean and user files are safe, re-run `node update-system.mjs apply` against a fixed upstream.

Example fix

# before: apply detected user-file edits upstream AND could not revert system paths
node update-system.mjs apply
# after: back up user files, repair git, roll back system paths manually
cp cv.md data/applications.md /tmp/user-backup/
rm -f .git/index.lock
git checkout -- modes/ templates/ *.mjs   # finish the system-path revert
# then restore user files from /tmp/user-backup/ — do NOT checkout them from HEAD
Defensive patterns

Strategy: try-catch

Validate before calling

// Before applying, verify upstream did not touch any USER_PATHS in the update
// range — the same check the runtime validator performs, but earlier.
import { execFileSync } from 'node:child_process';
function upstreamTouchedUserPaths(root, userPaths, baseRef, targetRef) {
  let diff;
  try {
    diff = execFileSync('git', ['diff', '--name-only', `${baseRef}..${targetRef}`],
      { cwd: root, encoding: 'utf-8' });
  } catch { return { error: 'cannot compute upstream diff' }; }
  const touched = diff.split('\n').filter(p => userPaths.some(u => p.startsWith(u)));
  return { touched };
}
// gate: if (touched.length) -> abort update, report upstream packaging bug.

Try / catch

// This is the most safety-sensitive error: preserve user files before any reset.
try {
  // run apply
} catch (err) {
  const msg = String(err?.message || err);
  if (msg.startsWith('Safety violation (') && msg.includes('and revert also failed')) {
    // 1. Back up the violated user files NOW (they mix your content + upstream edits).
    // 2. Repair git (err.cause is the violation; the revert error is in msg).
    // 3. Manually `git checkout -- <system paths>` to finish rollback.
    // 4. Restore user files from backup — NEVER from HEAD.
    throw err; // escalate; do not auto-reset.
  }
  throw err;
}

Prevention

When it happens

Trigger: An upstream commit incorrectly modified a path under USER_PATHS (a maintainer regression — e.g. editing data/applications.md or cv.md in the system branch) AND git checkout of the system paths fails in the same run. The violatedUserPaths set is populated (each file is logged as 'SAFETY VIOLATION: User file was modified'), revertPaths([...updated], ...) throws, and this combined error is raised.

Common situations: Upstream pushed a change that crossed the user/system boundary (the exact invariant the validator exists to catch) while the local repo is simultaneously in a state where git checkout fails — stale index.lock, full disk, dubious ownership, or a path conflict on case-insensitive filesystems. Rare because it needs both a packaging bug upstream and a git failure locally, but when it hits, the working tree is left with system paths at the new ref and user paths showing the upstream's (unwanted) edits, unsafely.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/291cd5757927d26f. Report an issue: GitHub.