abhigyanpatwari/GitNexus · error
Existing clone at ${targetDir} has no remote.origin — refusi
Error message
Existing clone at ${targetDir} has no remote.origin — refusing to pull What it means
cloneOrPull refuses to `git pull --ff-only` an existing checkout whose remote.origin.url cannot be read (getRemoteOriginUrl returned null — `git config --get remote.origin.url` produced nothing or the git process errored). The check exists because clone directories are keyed by URL basename; pulling without knowing the remote would update some unknown repository and silently analyze the wrong code (the wrong-repo vector from the adversarial review of PR #1325). This is an integrity failure of the on-disk clone state, not a network error.
Source
Thrown at gitnexus/src/server/git-clone.ts:285
*
* Closes the wrong-repo silent-analysis vector that Codex's adversarial
* review on PR #1325 surfaced: clone dirs are keyed by URL basename, so a
* request for `https://gitlab.example/attacker/repo.git` would otherwise
* collide with an existing `~/.gitnexus/repos/repo` cloned from a different
* origin and `git pull --ff-only` would silently succeed against the wrong
* remote.
*
* Exported so the comparison logic is testable in isolation against any
* tmpdir-based fixture, without needing to populate CLONE_ROOT.
*/
export async function assertRemoteMatchesRequestedUrl(
targetDir: string,
requestedUrl: string,
timeoutMs?: number,
): Promise<void> {
const remoteUrl = await getRemoteOriginUrl(targetDir, timeoutMs);
if (remoteUrl === null) {
throw new Error(`Existing clone at ${targetDir} has no remote.origin — refusing to pull`);
}
if (normalizeGitUrlForCompare(remoteUrl) !== normalizeGitUrlForCompare(requestedUrl)) {
throw new Error(
// Both URLs are echoed to the API caller and the server log, and either
// can carry `https://user:token@` userinfo — strip it here too (#2914).
`Existing clone at ${targetDir} has remote ${stripUrlCredentials(remoteUrl)}, ` +
`not the requested URL ${stripUrlCredentials(requestedUrl)}`,
);
}
}
/**
* Clone or pull a git repository.
* If targetDir doesn't exist: git clone --depth 1
* If targetDir exists with .git: git pull --ff-only (after verifying the
* existing clone's remote.origin matches the requested URL).
*
* Security:View on GitHub (pinned to 0d1aed942f)
Solutions
- Delete the broken clone directory (rm -rf ~/.gitnexus/repos/<name>, or the /data/gitnexus/repos path under GITNEXUS_HOME in Docker) so the next call does a fresh clone
- Or repair it in place: cd into the dir and `git remote add origin <url>` so remote.origin exists again
- Verify git is installed and on PATH for the server process — a failing `git config` spawn also yields null
- Guard future runs by always letting gitnexus derive the target via getCloneDir(extractRepoName(url)) instead of reusing arbitrary directories
Example fix
# before: broken clone dir with no remote.origin ls ~/.gitnexus/repos/myrepo/.git # exists, but `git -C ~/.gitnexus/repos/myrepo config --get remote.origin.url` prints nothing # after: remove so cloneOrPull re-clones cleanly rm -rf ~/.gitnexus/repos/myrepo # (or repair: git -C ~/.gitnexus/repos/myrepo remote add origin https://github.com/org/myrepo.git)
Defensive patterns
Strategy: validation
Validate before calling
import { getRemoteOriginUrl } from './git-clone.js';
const remote = await getRemoteOriginUrl(targetDir);
if (remote === null) {
// stale/broken checkout: clean it up instead of letting cloneOrPull throw
await fs.rm(targetDir, { recursive: true, force: true });
} Try / catch
try {
await cloneOrPull(url, dir);
} catch (err) {
if (err instanceof Error && err.message.includes('has no remote.origin')) {
await fs.rm(dir, { recursive: true, force: true });
await cloneOrPull(url, dir); // fresh clone now that the broken dir is gone
} else throw err;
} Prevention
- Always derive clone dirs from getCloneDir(extractRepoName(url)) so directory state matches what cloneOrPull expects
- Verify git is installed and on PATH for the server process — a failing `git config` spawn also yields a null remote
- Treat CLONE_ROOT as gitnexus-owned storage: never hand-init or copy repos into it
When it happens
Trigger: cloneOrPull(url, dir) where dir contains a .git directory but has no remote.origin configured — e.g. the directory was created by `git init` + manual copy, `git remote remove origin` was run, .git/config was truncated/corrupted, or the clone was made with a detached .git dir. Also triggered if the `git config` subprocess fails to spawn (proc error path resolves null).
Common situations: Stale or hand-crafted entries under CLONE_ROOT (~/.gitnexus/repos, or /data/gitnexus/repos in Docker); a repo copied with rsync/scp in a way that lost .git/config; CI containers where git is missing or PATH is broken so every git query resolves null; a previous clone interrupted mid-write leaving a broken .git.
Related errors
- Existing clone at ${targetDir} has remote ${stripUrlCredenti
- Invalid URL
- Only https:// and http:// git URLs are allowed
- Cloning from private/internal addresses is not allowed
- Clone target must be a subdirectory of ${CLONE_ROOT}
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08).
Data as JSON: /api/errors/d0866f28417e7241.
Report an issue: GitHub.