can1357/oh-my-pi · error · ToolError

could not find an unused worktree path under

Error message

could not find an unused worktree path under ${basePath} (tried ${WORKTREE_PATH_MAX_SUFFIX} suffixes)

What it means

resolveAvailableWorktreePath probes candidate paths (basePath, basePath-2, ... up to WORKTREE_PATH_MAX_SUFFIX) looking for a non-existent location for the new worktree. If every candidate exists (or stat errors other than ENOENT persist), it throws this ToolError after exhausting all suffixes.

Solutions

  1. Clean up stale worktrees: `git worktree list` then `git worktree remove <path>` (or delete leftover directories) and retry.
  2. Free/rename the occupied candidate paths under basePath so a suffix becomes available.
  3. Raise WORKTREE_PATH_MAX_SUFFIX or use a different base directory if this is a high-churn environment.

Example fix

# before: all candidates exist
$ git worktree list  # many stale entries pr-12, pr-12-2, ...
# after
$ git worktree remove ../project-pr-12 && git worktree prune
Defensive patterns

Strategy: try-catch

Validate before calling

import * as fs from "node:fs";
function worktreePathAvailable(basePath) {
  return !fs.existsSync(basePath) && !fs.existsSync(`${basePath}-2`);
}

Try / catch

try {
  await checkoutPullRequest({ cwd, pr });
} catch (err) {
  if (err instanceof ToolError && err.message.includes("unused worktree path")) {
    // prune stale worktrees then retry once
    await $`git worktree prune`.cwd(cwd);
  } else throw err;
}

Prevention

When it happens

Trigger: checkoutPullRequest called when a project directory accumulates many existing worktrees/dirs with the same base name — e.g. repeatedly checking out PRs that map to the same base path until all suffixed names are taken.

Common situations: Long-lived machine where prior PR checkouts were never cleaned up; leftover directories from crashed runs; a base path collision with an existing project folder name.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/d0fbdb98dd76b317. Report an issue: GitHub.

Appendix: source

Thrown at packages/coding-agent/src/tools/gh-pr-checkout.ts:113

export async function resolveAvailableWorktreePath(
	basePath: string,
	existingWorktrees: VcsWorktreeEntry[],
): Promise<string> {
	const registered = new Set(existingWorktrees.map(entry => path.resolve(entry.path)));
	for (let attempt = 0; attempt < WORKTREE_PATH_MAX_SUFFIX; attempt += 1) {
		const candidate = attempt === 0 ? basePath : `${basePath}-${attempt + 1}`;
		const normalized = path.resolve(candidate);
		if (registered.has(normalized)) continue;
		try {
			await fs.stat(normalized);
		} catch (error) {
			if (isEnoent(error)) {
				return candidate;
			}
			throw error;
		}
	}
	throw new ToolError(
		`could not find an unused worktree path under ${basePath} (tried ${WORKTREE_PATH_MAX_SUFFIX} suffixes)`,
	);
}

export function selectPrCloneUrl(originUrl: string | undefined, repo: Pick<GhRepoViewData, "url" | "sshUrl">): string {
	if (originUrl?.startsWith("http://") || originUrl?.startsWith("https://")) {
		return normalizeOptionalString(repo.url) ?? normalizeOptionalString(repo.sshUrl) ?? "";
	}

	return normalizeOptionalString(repo.sshUrl) ?? normalizeOptionalString(repo.url) ?? "";
}

export async function getRemoteUrls(repoRoot: string, signal?: AbortSignal): Promise<Map<string, string>> {
	const repo = vcs.requireGit(repoRoot);
	const remotes = await repo.remoteList(signal);
	const urls = new Map<string, string>();
	for (const remoteName of remotes) {
		const remoteUrl = await repo.remoteUrl(remoteName, signal);

View on GitHub (pinned to 9690622007)