{"record":{"id":"291cd5757927d26f","repo":"santifer/career-ops","slug":"safety-violation-violation-message-and-revert","errorCode":null,"errorMessage":"Safety violation (${violation.message}) and revert also failed (${revertErr.message})","messagePattern":"Safety violation \\((.+?)\\) and revert also failed \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"update-system.mjs","lineNumber":1313,"sourceCode":"      throw err;\n    }\n\n    if (violatedUserPaths.size > 0) {\n      console.error('Aborting: user files were touched. Rolling back system files...');\n      // Revert ONLY the system-layer updates — never `git checkout` the\n      // violated user paths back to HEAD. Doing so would overwrite the\n      // user's working-tree content (accumulated STAR+R stories, local\n      // edits) with whatever is committed upstream, causing data loss.\n      // The user files were flagged as touched by the update, not by the\n      // user; leaving them as-is is the safe choice — the user decides\n      // what to do with them.\n      const violation = new Error('Update aborted: user files were touched.');\n      try {\n        revertPaths([...updated], initialStatusPaths);\n      } catch (revertErr) {\n        // If the revert itself fails, don't lose the safety-violation\n        // diagnostic — chain it via `cause` so the user sees both.\n        throw new Error(\n          `Safety violation (${violation.message}) and revert also failed (${revertErr.message})`,\n          { cause: violation },\n        );\n      }\n      console.error(`User file(s) left as-is (your content was NOT overwritten):`);\n      for (const f of violatedUserPaths) console.error(`  ${f}`);\n      // `throw` (not `process.exit`) so the outer `finally` runs and\n      // .update-lock is removed. Exiting here would leak the lock and\n      // permanently block subsequent updates until the user deletes\n      // it manually.\n      throw violation;\n    }\n\n    // 5. Install any new dependencies\n    try {\n      execSync('npm install --silent', { cwd: ROOT, timeout: NPM_INSTALL_TIMEOUT_MS });\n    } catch {\n      console.log('npm install skipped (may need manual run)');","sourceCodeStart":1295,"sourceCodeEnd":1331,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/update-system.mjs#L1295-L1331","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: apply detected user-file edits upstream AND could not revert system paths\nnode update-system.mjs apply\n# after: back up user files, repair git, roll back system paths manually\ncp cv.md data/applications.md /tmp/user-backup/\nrm -f .git/index.lock\ngit checkout -- modes/ templates/ *.mjs   # finish the system-path revert\n# then restore user files from /tmp/user-backup/ — do NOT checkout them from HEAD","handlingStrategy":"try-catch","validationCode":"// Before applying, verify upstream did not touch any USER_PATHS in the update\n// range — the same check the runtime validator performs, but earlier.\nimport { execFileSync } from 'node:child_process';\nfunction upstreamTouchedUserPaths(root, userPaths, baseRef, targetRef) {\n  let diff;\n  try {\n    diff = execFileSync('git', ['diff', '--name-only', `${baseRef}..${targetRef}`],\n      { cwd: root, encoding: 'utf-8' });\n  } catch { return { error: 'cannot compute upstream diff' }; }\n  const touched = diff.split('\\n').filter(p => userPaths.some(u => p.startsWith(u)));\n  return { touched };\n}\n// gate: if (touched.length) -> abort update, report upstream packaging bug.","typeGuard":null,"tryCatchPattern":"// This is the most safety-sensitive error: preserve user files before any reset.\ntry {\n  // run apply\n} catch (err) {\n  const msg = String(err?.message || err);\n  if (msg.startsWith('Safety violation (') && msg.includes('and revert also failed')) {\n    // 1. Back up the violated user files NOW (they mix your content + upstream edits).\n    // 2. Repair git (err.cause is the violation; the revert error is in msg).\n    // 3. Manually `git checkout -- <system paths>` to finish rollback.\n    // 4. Restore user files from backup — NEVER from HEAD.\n    throw err; // escalate; do not auto-reset.\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["safety-invariant","update-system","git","data-loss-prevention","atomicity","user-layer"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}