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

Assignment cwd changed for task {task.id}

Error message

Assignment cwd changed for task {task.id}

What it means

After confirming an assignment's task is active, the harness recomputes the task's worktree cwd and compares it (resolved) to the cwd recorded when the lease was granted. A mismatch means the on-disk worktree location for that task changed under the lease — e.g. the worktree was recreated at a different path, or the recorded cwd pointed at a since-moved directory. Continuing would run commands in the wrong directory, so it aborts.

Source

Thrown at s15_integrated_harness/code.py:482

    with task_lock:
        assignment = teammate_assignments.get(owner)
        task = _owner_in_progress(owner)
        if task and (not assignment or assignment.get("task_id") != task.id):
            cwd, error = task_worktree_cwd(task)
            if error:
                raise ValueError(error)
            assignment = {"task_id": task.id, "cwd": cwd}
            teammate_assignments[owner] = assignment
        elif not assignment:
            return WORKDIR
        task = load_task(str(assignment["task_id"]))
        if task.status not in {"in_progress", "completed"} or task.owner != owner:
            raise ValueError(f"Assignment for {owner} is no longer active")
        cwd, error = task_worktree_cwd(task)
        if error:
            raise ValueError(error)
        if cwd.resolve() != Path(assignment["cwd"]).resolve():
            raise ValueError(f"Assignment cwd changed for task {task.id}")
        return cwd


def release_completed_assignment(owner: str) -> bool:
    """Release a completed cwd lease only at a model turn boundary."""
    with task_lock:
        assignment = teammate_assignments.get(owner)
        if not assignment:
            return False
        task = load_task(str(assignment["task_id"]))
        if task.status != "completed" or task.owner != owner:
            return False
        teammate_assignments.pop(owner, None)
        advance_assignment_version(owner)
        if owner in globals().get("plan_gates", {}):
            globals()["plan_gates"][owner] = "not_required"
        return True

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Do not remove/recreate a worktree while its task is in_progress; finish or reassign the task first.
  2. Restart the harness after moving the workspace so absolute cwds in assignments are regenerated.
  3. Change a task's worktree only through the harness APIs so the lease updates atomically.
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path

def worktree_stable(task, recorded_cwd: str) -> bool:
    cwd, err = task_worktree_cwd(task)
    if err:
        return False
    return cwd.resolve() == Path(recorded_cwd).resolve()

Try / catch

try:
    cwd = _agent_cwd_for(owner)
except ValueError as e:
    if "cwd changed" in str(e):
        # worktree was moved/recreated: re-establish the lease via restart
        raise

Prevention

When it happens

Trigger: Deleting and recreating a task's worktree under a different name/path while its owner holds a lease; task.worktree field edited between turns; WORKTREES_DIR restructured; the recorded absolute cwd became stale after moving the whole workspace.

Common situations: Agents running 'git worktree prune/remove + add' on an in-use worktree; moving or cloning the workspace to a new path without restarting sessions; hand-editing task JSON's worktree field.

Related errors


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