abhigyanpatwari/GitNexus · error · Error
Clone target already exists but is not a git repository: ${s
Error message
Clone target already exists but is not a git repository: ${safeTarget} What it means
When the target is not (yet) recognized as a git repository and targetDir already exists non-empty, cloneOrPull refuses to clone over it. Cloning into a non-empty, non-git directory would either fail or mix foreign files into the checkout, so the library fails fast instead.
Source
Thrown at gitnexus/src/server/git-clone.ts:450
// ignored paths must survive, and `-e /.gitnexus` is belt-and-braces
// because `.git/info/exclude` is skipped on a read-only storage mount
// and a freshly cloned repo may not have been analyzed yet at all.
await runGitImpl(['clean', '--force', '-d', '-e', '/.gitnexus'], safeTarget, {
token: options?.token,
url,
timeoutMs: options?.timeoutMs,
});
}
} else {
await runGitImpl(['pull', '--ff-only'], safeTarget, {
token: options?.token,
url,
timeoutMs: options?.timeoutMs,
});
}
} else {
if (targetExists && (await fs.readdir(safeTarget)).length > 0) {
throw new Error(`Clone target already exists but is not a git repository: ${safeTarget}`);
}
onProgress?.({ phase: 'cloning', message: `Cloning ${url}...` });
try {
const runGitImpl = options?.runGitForTest ?? runGit;
const cloneArgs = options?.branch
? buildBranchCloneArgs(url, safeTarget, options.branch)
: buildCloneArgs(url, safeTarget);
await runGitImpl(cloneArgs, undefined, {
token: options?.token,
url,
timeoutMs: options?.timeoutMs,
});
await assertPostRealpathContainment(cloneRoot, safeTarget);
} catch (err: unknown) {
if (options?.quarantineRoot) {
const partialExists = await fs.access(safeTarget).then(
() => true,
() => false,View on GitHub (pinned to 0d1aed942f)
Solutions
- Delete or empty the target directory, then retry the clone.
- Re-initialize it as a proper git repo (git init + set remote) if the content should be preserved, or clone elsewhere and migrate.
- Point targetDir at a fresh path inside the clone root.
- Investigate what left the non-git files there before removing them.
Example fix
// before
await cloneOrPull({ url, targetDir: '/clones/repo' }); // dir has stale files, no .git
// after
await fs.rm('/clones/repo', { recursive: true, force: true });
await cloneOrPull({ url, targetDir: '/clones/repo' }); Defensive patterns
Strategy: validation
Validate before calling
import { fs } from '...'; // node:fs/promises
let isRepo = false, empty = true;
try {
await fs.access(path.join(targetDir, '.git'));
isRepo = true;
} catch {}
try {
empty = (await fs.readdir(targetDir)).length === 0;
} catch {}
if (!isRepo && !empty) throw new Error(`Clean or remove non-git directory ${targetDir} before cloning`); Try / catch
try {
await cloneOrPull(opts);
} catch (err) {
if ((err as Error).message.startsWith('Clone target already exists but is not a git repository')) {
await fs.rm(opts.targetDir, { recursive: true, force: true });
return cloneOrPull(opts);
}
throw err;
} Prevention
- Clone only into fresh, managed directories inside the clone root.
- Never point clone targets at pre-existing project folders.
- After a failed clone, let the quarantine logic clean up instead of manually deleting only .git.
- Monitor for processes (e.g. antivirus) that delete .git directories.
When it happens
Trigger: targetDir exists and contains files but has no .git directory (deleted .git, half-deleted clone, or a directory that was never a repo); a previous failed run left partial files after quarantine was skipped; the user pointed targetDir at an unrelated folder.
Common situations: Manual 'rm -rf .git' or antivirus deleting .git; a previous clone failing mid-write; reusing an existing project folder as the clone destination; path collisions from a previous differently-named clone layout.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- Clone failed and partial checkout could not be quarantined:
- Clone target repo name ${expectedRepoName} does not match re
- Clone target basename must match repository name ${expectedR
- Clone target must be a subdirectory of ${CLONE_ROOT}
- parsedfile-cache: could not reset durable chunk generation;
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08).
Data as JSON: /api/errors/ec5407888a1b3d0a.
Report an issue: GitHub.