santifer/career-ops · error
career-ops at is not a git checkout of its own, so git…
Error message
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. What it means
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.
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.
Example fix
# before (ZIP-unpacked install inside another repo)
~/myrepo/career-ops/ # no .git of its own
# after
git clone https://github.com/career-ops/career-ops.git ~/career-ops
cp -r ~/myrepo/career-ops/{cv.md,config,data,reports} ~/career-ops/ Defensive patterns
Strategy: validation
Validate before calling
import { execFileSync } from 'node:child_process';
import { join } from 'node:path';
const toplevel = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: root, encoding: 'utf-8' }).trim();
if (toplevel !== root) {
throw new Error(`career-ops install at ${root} is not its own git checkout (toplevel: ${toplevel}); re-clone before updating`);
} Type guard
function isOwnGitCheckout(root) {
try {
const top = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: root, encoding: 'utf-8' }).trim();
return top === root;
} catch { return false; }
} Try / catch
try {
// apply() or rollback()
} catch (err) {
if (err.message.includes('not a git checkout of its own')) {
console.error('Re-clone career-ops and migrate user-layer files per DATA_CONTRACT.md before updating.');
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.
Related errors
- timed out after s. If your network is slow, retry or set to…
- a 40-hex commit --sha is required
- addPaths received directory pathspec(s), which -f would…
- clone of @ failed
- could not read the release pointer
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/bdec3d2a0dadd9b0.
Report an issue: GitHub.
Appendix: source
Thrown at update-system.mjs:849
const canonicalize = realpathSync.native ?? realpathSync;
let same;
try {
same = canonicalize(toplevel) === canonicalize(root);
} catch {
same = resolve(toplevel) === resolve(root);
}
return same ? null : toplevel;
}
/**
* Throw when git operations from ROOT would land in an enclosing repository.
* First statement of apply() and rollback(); check() reports a status instead.
*/
function assertOwnGitToplevel() {
const foreignToplevel = gitToplevelMismatch();
if (foreignToplevel) {
throw new Error(
`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.`,
);
}
}
/**
* Paths the target manifest ships that did not materialize on disk.
*
* apply() reports success without checking that the checkout loop actually
* produced a coherent install, so a client whose local manifest predates the
* target's silently ends up missing every path added since — and only finds
* out when the next script crashes with ERR_MODULE_NOT_FOUND (#1998).
*
* @param {string[]} targetPaths - SYSTEM_PATHS read from the target updater.
* @returns {string[]} Entries present in FETCH_HEAD but absent locally.
*/
function missingFromTargetManifest(targetPaths) {
const missing = [];
for (const path of targetPaths) {View on GitHub (pinned to e7abd431fc)