shareAI-lab/learn-claude-code · error · ValueError

Worktree '{name}' already exists in index

Error message

Worktree '{name}' already exists in index

What it means

Thrown by WorktreeManager.create when _find(name) locates an entry with the same name in the persisted worktree index JSON. Creation is idempotent-hostile by design: each worktree name maps to one directory (self.dir / name) and one branch (wt/{name}), so a duplicate would overwrite state. The check reads the index file, so it also fires when a previous worktree was created but never removed from the index.

Source

Thrown at agents/s12_worktree_task_isolation.py:287

        self.index_path.write_text(json.dumps(data, indent=2))

    def _find(self, name: str) -> dict | None:
        idx = self._load_index()
        for wt in idx.get("worktrees", []):
            if wt.get("name") == name:
                return wt
        return None

    def _validate_name(self, name: str):
        if not re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name or ""):
            raise ValueError(
                "Invalid worktree name. Use 1-40 chars: letters, numbers, ., _, -"
            )

    def create(self, name: str, task_id: int = None, base_ref: str = "HEAD") -> str:
        self._validate_name(name)
        if self._find(name):
            raise ValueError(f"Worktree '{name}' already exists in index")
        if task_id is not None and not self.tasks.exists(task_id):
            raise ValueError(f"Task {task_id} not found")

        path = self.dir / name
        branch = f"wt/{name}"
        self.events.emit(
            "worktree.create.before",
            task={"id": task_id} if task_id is not None else {},
            worktree={"name": name, "base_ref": base_ref},
        )
        try:
            self._run_git(["worktree", "add", "-b", branch, str(path), base_ref])

            entry = {
                "name": name,
                "path": str(path),
                "branch": branch,
                "task_id": task_id,

View on GitHub (pinned to 985456f4ad)

Solutions

  1. If the existing worktree is still wanted, reuse its entry from the index instead of creating a new one
  2. If it is stale, call the manager's remove/delete flow for that name first, then create again
  3. Make names unique per task, e.g. f"task-{task_id}-{slug}", so parallel tasks cannot collide
  4. Catch ValueError and inspect the index entry to decide reuse-vs-replace programmatically

Example fix

// before
wt.create(name="feature-x", task_id=7)  # raises on retry
// after
existing = wt._find("feature-x")
if existing:
    result = existing["path"]  # reuse
else:
    result = wt.create(name="feature-x", task_id=7)
Defensive patterns

Strategy: validation

Validate before calling

entry = wt._find(name)
if entry:
    use_path = entry["path"]  # reuse instead of create
else:
    use_path = wt.create(name, task_id=tid)

Try / catch

try:
    path = wt.create(name, task_id=tid)
except ValueError as e:
    if 'already exists' in str(e):
        existing = wt._find(name)
        if existing and stale(existing):
            wt.remove(name)
            path = wt.create(name, task_id=tid)
        else:
            path = existing["path"]
    else:
        raise

Prevention

When it happens

Trigger: Calling create("feature-x") twice without a matching remove/delete in between; a prior create that succeeded on disk but whose caller crashed before the flow completed, leaving the index entry behind; two agents racing to create the same-named worktree.

Common situations: Retrying a failed orchestration step that re-runs the same create call; stale index entries after interrupted cleanup; deriving the same slug from two different task subjects that sanitize to an identical name.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/b43f8dc61d16ca80. Report an issue: GitHub.