abhigyanpatwari/GitNexus · error
Clone target basename must match repository name ${expectedR
Error message
Clone target basename must match repository name ${expectedRepoName} What it means
cloneOrPull throws this when the basename of the resolved targetDir does not equal the supplied expectedRepoName. Together with the URL-name check this guarantees the clone lands in a directory named exactly after the repository, so downstream consumers can rely on targetDir === cloneRoot/<repoName>.
Source
Thrown at gitnexus/src/server/git-clone.ts:342
onProgress?: (progress: CloneProgress) => void,
options?: CloneOrPullOptions,
): Promise<string> {
// Containment barrier — inline with the canonical path.relative idiom so
// CodeQL recognizes the sanitizer at every following filesystem and
// subprocess sink. The same `safeTarget` is used for every downstream
// path operation — no reassignment that the analyzer could lose track of.
//
// The lexical check runs before filesystem creation; realpath and symlink
// checks below run before pull/clone and again after clone completes.
const cloneRoot = path.resolve(options?.allowedCloneRoot ?? CLONE_ROOT);
const expectedRepoName = options?.expectedRepoName;
if (expectedRepoName !== undefined && expectedRepoName !== extractRepoName(url)) {
throw new Error(`Clone target repo name ${expectedRepoName} does not match requested URL`);
}
const safeTarget = path.resolve(targetDir);
if (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {
throw new Error(`Clone target basename must match repository name ${expectedRepoName}`);
}
const rel = path.relative(cloneRoot, safeTarget);
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
throw new Error(`Clone target must be a subdirectory of ${cloneRoot}`);
}
// Always validate the requested URL — the prior shape only ran this in
// the code path where the repo was cloned. Now it runs unconditionally,
// preventing SSRF / blocked-host bypasses even when targetDir already exists.
if (options?.allowAutoSyncSsh) validateAutoSyncRemoteUrl(url);
else validateGitUrl(url);
await fs.mkdir(cloneRoot, { recursive: true });
if (options?.allowedCloneRoot) {
await assertDirectoryOwnerAndPermissions(cloneRoot);
}
await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));
await fs.mkdir(path.dirname(safeTarget), { recursive: true });View on GitHub (pinned to 0d1aed942f)
Solutions
- Set targetDir to a directory whose basename equals expectedRepoName (e.g. path.join(cloneRoot, expectedRepoName)).
- Remove any '-v2', date, or fork suffixes from the target directory name.
- Update expectedRepoName if the on-disk layout name is the intended source of truth.
- Stop passing expectedRepoName if the basename constraint should not apply.
Example fix
// before
await cloneOrPull({ url, expectedRepoName: 'gitnexus', targetDir: '/clones/gitnexus-mirror' });
// after
await cloneOrPull({ url, expectedRepoName: 'gitnexus', targetDir: '/clones/gitnexus' }); Defensive patterns
Strategy: validation
Validate before calling
const safeTarget = path.resolve(targetDir);
if (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {
throw new Error(`targetDir basename must be ${expectedRepoName}`);
} Try / catch
try {
await cloneOrPull(opts);
} catch (err) {
if ((err as Error).message.startsWith('Clone target basename')) {
opts.targetDir = path.join(path.dirname(opts.targetDir), opts.expectedRepoName);
return cloneOrPull(opts);
}
throw err;
} Prevention
- Always build targetDir as path.join(cloneRoot, expectedRepoName).
- Avoid suffixes/version tags in clone directory names; use separate clone roots per layout instead.
- Enforce the <root>/<repoName> layout convention in code review.
When it happens
Trigger: Passing a targetDir like /clones/my-fork when expectedRepoName is 'my-repo'; appending a suffix such as /clones/my-repo-v2 to targetDir; reusing a cached targetDir built for a differently named repo.
Common situations: Versioned or suffixed local directories after a host-side repo rename; callers computing targetDir from user input while expectedRepoName comes from trusted config; migrating clone layouts to a new naming scheme.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Clone target repo name ${expectedRepoName} does not match re
- Clone target already exists but is not a git repository: ${s
- Clone failed and partial checkout could not be quarantined:
- Path must not be empty
- Claude CLI not found. Install Claude Code and ensure `claude
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-09-08).
Data as JSON: /api/errors/98ca2e276f3827f9.
Report an issue: GitHub.