abhigyanpatwari/GitNexus · error · Error

Clone target already exists but is not a git repository

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.

Solutions

  1. Delete or empty the target directory, then retry the clone.
  2. Re-initialize it as a proper git repo (git init + set remote) if the content should be preserved, or clone elsewhere and migrate.
  3. Point targetDir at a fresh path inside the clone root.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-08). Data as JSON: /api/errors/ec5407888a1b3d0a. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/server/git-clone.ts:649

        // as if they were part of the remote commit. Deliberately no `-x`/`-X`:
        // 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, gitOpts);
      }
    } else if (options?.branch && requestedRefMatch === 'branch') {
      await restoreGitNexusGeneratedOverlays(runGitImpl, safeTarget, gitOpts);
      await fetchAndCheckoutRequestedBranch(runGitImpl, safeTarget, options.branch, gitOpts);
    } else if (options?.branch && requestedRefMatch === 'sha') {
      // Tag / SHA pin: already at the requested commit. Fetching
      // `refs/heads/<name>` would follow a same-named branch past the pin.
      await restoreGitNexusGeneratedOverlays(runGitImpl, safeTarget, gitOpts);
    } else {
      await runGitImpl(['pull', '--ff-only'], safeTarget, gitOpts);
    }
  } 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 ac9a4e9abd)