GoogleContainerTools/skaffold · warning

failed to remove repo cache dir: %w

Error message

failed to remove repo cache dir: %w

What it means

Thrown by syncRepo when, after a branch-less fallback clone succeeded, `git checkout <ref>` inside the fresh clone fails AND the cleanup os.RemoveAll(repoCacheDir) also fails. The RemoveAll error is wrapped into this message and stuffed into err, so the developer sees why cleanup failed. The user still gets the checkout failure wrapped in 'failed to checkout commit'.

Source

Thrown at pkg/skaffold/git/gitutil.go:136

	hash, err := getRepoDir(g)
	if err != nil {
		return "", fmt.Errorf("failed to clone git repo: unable to create directory name: %w", err)
	}
	repoCacheDir := filepath.Join(skaffoldCacheDir, hash)
	if _, err := os.Stat(repoCacheDir); os.IsNotExist(err) {
		if opts.SyncRemoteCache.CloneDisabled() {
			return "", SyncDisabledErr(g, repoCacheDir)
		}
		if _, err := r.Run(ctx, "clone", g.RepoCloneURI, fmt.Sprintf("./%s", hash), "--branch", ref, "--depth", "1"); err != nil {
			if strings.Contains(err.Error(), "Could not find remote branch") {
				if _, err := r.Run(ctx, "clone", g.RepoCloneURI, fmt.Sprintf("./%s", hash), "--depth", "1"); err != nil {
					return "", fmt.Errorf("failed to clone repo: %w", err)
				}

				r.Dir = repoCacheDir
				if _, err := r.Run(ctx, "checkout", ref); err != nil {
					if rmErr := os.RemoveAll(repoCacheDir); rmErr != nil {
						err = fmt.Errorf("failed to remove repo cache dir: %w", rmErr)
					}

					return "", fmt.Errorf("failed to checkout commit: %w", err)
				}
			} else {
				return "", fmt.Errorf("failed to clone repo: %w", err)
			}
		}
	} else {
		r.Dir = repoCacheDir
		// check remote is defined
		if remotes, err := r.Run(ctx, "remote", "-v"); err != nil {
			return "", fmt.Errorf("failed to clone repo %s: trouble checking repository remote; run 'git clone <REPO>; stat <DIR/SUBDIR>' to verify credentials: %w", g.Repo, err)
		} else if len(remotes) == 0 {
			return "", fmt.Errorf("failed to clone repo %s: remote not set for existing clone", g.Repo)
		}

		// if sync property is false, then skip fetching latest from remote and resetting the branch.

View on GitHub (pinned to a1189de023)

Solutions

  1. Delete the stale/partial cache dir manually: rm -rf <cache-dir>/<hash>, then retry.
  2. Verify the ref exists: git ls-remote <URI> <ref> — correct typos or use the full ref name (refs/heads/<ref>).
  3. If RemoveAll keeps failing, check mounts/permissions on the cache directory and ensure no process locks files there.
  4. Set ref explicitly in config so the branch-less fallback + checkout path is avoided.

Example fix

// before
repo: https://github.com/org/repo#Mian    # typo, checkout fails
// after
repo: https://github.com/org/repo#main
Defensive patterns

Strategy: try-catch

Validate before calling

const { execSync } = require('child_process');
function refExists(uri, ref) {
  try { execSync(`git ls-remote --exit-code ${uri} ${ref}`, { stdio: 'pipe' }); return true; }
  catch { return false; }
}

Type guard

null

Try / catch

try {
  await syncRepo(g, ctx, opts);
} catch (err) {
  if (/failed to checkout commit|failed to remove repo cache dir/.test(err.message)) {
    // cleanup the poisoned cache dir ourselves, then retry once
    fs.rmSync(path.join(cacheDir, hashFor(g)), { recursive: true, force: true });
    return syncRepo(g, ctx, opts);
  }
  throw err;
}

Prevention

When it happens

Trigger: Ref not found in the cloned default branch after the fallback clone, plus os.RemoveAll(repoCacheDir) failing to delete the half-cloned cache dir (files held open, permission loss, NFS stale handles).

Common situations: Typo'd ref (e.g. 'Main' vs 'main'); ref exists on another remote but not origin; cache dir on a network filesystem where RemoveAll fails mid-way; permissions changed by a concurrent process.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/3a66948bc04184e2. Report an issue: GitHub.