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

  1. Set targetDir to a directory whose basename equals expectedRepoName (e.g. path.join(cloneRoot, expectedRepoName)).
  2. Remove any '-v2', date, or fork suffixes from the target directory name.
  3. Update expectedRepoName if the on-disk layout name is the intended source of truth.
  4. 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

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


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