{"record":{"id":"bdec3d2a0dadd9b0","repo":"santifer/career-ops","slug":"career-ops-at-root-is-not-a-git-checkout-of-its-own-so-git","errorCode":null,"errorMessage":"career-ops at ${ROOT} is not a git checkout of its own, so git operations would land in the enclosing repository at ${foreignToplevel} — this happens when the install was unpacked from a ZIP or copied without its .git directory. Nothing was changed. To make updates work, clone career-ops fresh (git clone ${CANONICAL_REPO}) and move your user-layer files (cv.md, config/, data/, reports/ — see DATA_CONTRACT.md) into the new clone.","messagePattern":"career-ops at (.+?) is not a git checkout of its own, so git operations would land in the enclosing repository at (.+?) — this happens when the install was unpacked from a ZIP or copied without its \\.git directory\\. Nothing was changed\\. To make updates work, clone career-ops fresh \\(git clone (.+?)\\) and move your user-layer files \\(cv\\.md, config/, data/, reports/ — see DATA_CONTRACT\\.md\\) into the new clone\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"update-system.mjs","lineNumber":849,"sourceCode":"  const canonicalize = realpathSync.native ?? realpathSync;\n  let same;\n  try {\n    same = canonicalize(toplevel) === canonicalize(root);\n  } catch {\n    same = resolve(toplevel) === resolve(root);\n  }\n  return same ? null : toplevel;\n}\n\n/**\n * Throw when git operations from ROOT would land in an enclosing repository.\n * First statement of apply() and rollback(); check() reports a status instead.\n */\nfunction assertOwnGitToplevel() {\n  const foreignToplevel = gitToplevelMismatch();\n  if (foreignToplevel) {\n    throw new Error(\n      `career-ops at ${ROOT} is not a git checkout of its own, so git operations would land in the enclosing repository at ${foreignToplevel} — this happens when the install was unpacked from a ZIP or copied without its .git directory. Nothing was changed. To make updates work, clone career-ops fresh (git clone ${CANONICAL_REPO}) and move your user-layer files (cv.md, config/, data/, reports/ — see DATA_CONTRACT.md) into the new clone.`,\n    );\n  }\n}\n\n/**\n * Paths the target manifest ships that did not materialize on disk.\n *\n * apply() reports success without checking that the checkout loop actually\n * produced a coherent install, so a client whose local manifest predates the\n * target's silently ends up missing every path added since — and only finds\n * out when the next script crashes with ERR_MODULE_NOT_FOUND (#1998).\n *\n * @param {string[]} targetPaths - SYSTEM_PATHS read from the target updater.\n * @returns {string[]} Entries present in FETCH_HEAD but absent locally.\n */\nfunction missingFromTargetManifest(targetPaths) {\n  const missing = [];\n  for (const path of targetPaths) {","sourceCodeStart":831,"sourceCodeEnd":867,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/update-system.mjs#L831-L867","documentation":"assertOwnGitToplevel guards update-system's apply() and rollback(): before any mutating git operation it verifies that the career-ops install directory is its own git repository. If git's toplevel for ROOT resolves to an enclosing repository (foreignToplevel), running updates would commit/push into that unrelated repo, so the function throws with remediation instructions and performs no changes.","triggerScenarios":"Calling apply() or rollback() in update-system.mjs when the install directory lacks its own .git — e.g. the tree was unzipped from a release archive or copied without .git — while sitting inside some other git repository, so gitToplevelMismatch() returns the enclosing repo path.","commonSituations":"Downloading the career-ops ZIP from GitHub instead of git clone and unpacking it inside a home directory or project that is itself a git repo; rsync/scp'ing an install to a new machine without the .git directory; nesting a career-ops checkout inside a dotfiles repo.","solutions":["Clone career-ops fresh with git clone <CANONICAL_REPO> so it has its own .git directory.","Move user-layer files (cv.md, config/, data/, reports/, jds/, output/ — see DATA_CONTRACT.md) from the old install into the new clone.","Re-point any configuration (CAREER_OPS_ROOT, marker files, cron jobs) at the new clone and delete the .git-less copy.","As a last resort, git init inside the install and add the canonical repo as origin — but a fresh clone is the supported path."],"exampleFix":"# before (ZIP-unpacked install inside another repo)\n~/myrepo/career-ops/   # no .git of its own\n\n# after\ngit clone https://github.com/career-ops/career-ops.git ~/career-ops\ncp -r ~/myrepo/career-ops/{cv.md,config,data,reports} ~/career-ops/","handlingStrategy":"validation","validationCode":"import { execFileSync } from 'node:child_process';\nimport { join } from 'node:path';\nconst toplevel = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: root, encoding: 'utf-8' }).trim();\nif (toplevel !== root) {\n  throw new Error(`career-ops install at ${root} is not its own git checkout (toplevel: ${toplevel}); re-clone before updating`);\n}","typeGuard":"function isOwnGitCheckout(root) {\n  try {\n    const top = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: root, encoding: 'utf-8' }).trim();\n    return top === root;\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  // apply() or rollback()\n} catch (err) {\n  if (err.message.includes('not a git checkout of its own')) {\n    console.error('Re-clone career-ops and migrate user-layer files per DATA_CONTRACT.md before updating.');\n  } else throw err;\n}","preventionTips":["Install career-ops with git clone, never by unzipping a release archive into another repo.","Verify the install has its own .git directory after moving it between machines.","Keep career-ops checkouts out of enclosing git repositories (dotfiles, monorepos).","Back up user-layer files (cv.md, config/, data/, reports/) before any reinstall or re-clone."],"tags":["git","update-system","install","state-guard"],"backgroundTag":"git-command-failed","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}