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
- 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.
- 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.
- 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.
- 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.
- 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
- Commit or back up user-layer files (cv.md, data/, reports/, interview-prep/) before running apply — the validator protects you, but defense in depth matters.
- Pin to a reviewed upstream ref rather than auto-tracking HEAD; USER_PATHS edits upstream are a packaging regression.
- Keep the local git tree clean before an update so the validator's initialStatusPaths baseline is accurate.
- Treat any 'SAFETY VIOLATION' log line as a real incident: back up, then repair — do not re-run apply blind.
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
- Validation failed (${err.message}) and revert also failed ($
- ${describeGitCommand(args)} timed out after ${timeoutSeconds
- a 40-hex commit --sha is required (got ${JSON.stringify(sha)
- clone of ${url}@${sha.slice(0, 10)} failed — ${err.stderr ?
- MODE_MISSING
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/291cd5757927d26f.
Report an issue: GitHub.