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 the assignment's task is still active, the system recomputes the task's worktree cwd and compares it (resolved) against the cwd stored when the lease was created. If they differ — the worktree was recreated, renamed, or moved — the lease's recorded cwd no longer matches reality and the mismatch is surfaced as ValueError instead of executing work in the wrong directory.
Source
Thrown at s13_agent_teams/code.py:409
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
- Don't delete/recreate .worktrees entries for tasks that have active assignments; release the assignment first.
- Catch this ValueError per-owner, drop the stale lease (teammate_assignments.pop(owner)) so the next call re-derives the cwd, and retry once.
- Keep the workspace path stable (fully resolved) for the process lifetime.
Example fix
# before
def owner_cwd(owner):
return cwd_for(owner) # propagates ValueError
# after
def owner_cwd(owner):
try:
return cwd_for(owner)
except ValueError:
with task_lock:
teammate_assignments.pop(owner, None)
return cwd_for(owner) # re-derives a fresh lease Defensive patterns
Strategy: fallback
Validate before calling
from pathlib import Path
def lease_cwd_matches(owner: str) -> bool:
with task_lock:
a = teammate_assignments.get(owner) or {}
try:
t = load_task(str(a.get('task_id', '')))
cwd, err = task_worktree_cwd(t)
except (ValueError, TypeError):
return False
return not err and cwd.resolve() == Path(a.get('cwd', '/nonexistent')).resolve() Try / catch
try:
cwd = owner_cwd(owner)
except ValueError as exc:
if 'Assignment cwd changed' in str(exc):
with task_lock:
teammate_assignments.pop(owner, None)
return owner_cwd(owner) # one re-derivation; if it fails again, surface it
raise Prevention
- Never wipe .worktrees for tasks with live assignments; release assignments first.
- After any worktree maintenance, clear assignment leases so cwds are re-derived.
- Keep workspace paths stable for the process lifetime.
When it happens
Trigger: Deleting and recreating the worktree for the same task at a different path; the worktree name derivation changing between versions; WORKDIR resolution changing so the same worktree resolves to a different absolute path.
Common situations: Cleanup scripts that wipe .worktrees while agents hold assignments; version upgrades that alter worktree naming; workspace relocation under a changing symlink.
Related errors
- Assignment for {owner} is no longer active
- Worktree path escapes directory: {name!r}
- Assignment cwd changed for task {task.id}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/130cafc0e37732bd.
Report an issue: GitHub.