{"record":{"id":"130cafc0e37732bd","repo":"shareAI-lab/learn-claude-code","slug":"assignment-cwd-changed-for-task-task-id","errorCode":null,"errorMessage":"Assignment cwd changed for task {task.id}","messagePattern":"Assignment cwd changed for task (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s13_agent_teams/code.py","lineNumber":409,"sourceCode":"    with task_lock:\n        assignment = teammate_assignments.get(owner)\n        task = _owner_in_progress(owner)\n        if task and (not assignment or assignment.get(\"task_id\") != task.id):\n            cwd, error = task_worktree_cwd(task)\n            if error:\n                raise ValueError(error)\n            assignment = {\"task_id\": task.id, \"cwd\": cwd}\n            teammate_assignments[owner] = assignment\n        elif not assignment:\n            return WORKDIR\n        task = load_task(str(assignment[\"task_id\"]))\n        if task.status not in {\"in_progress\", \"completed\"} or task.owner != owner:\n            raise ValueError(f\"Assignment for {owner} is no longer active\")\n        cwd, error = task_worktree_cwd(task)\n        if error:\n            raise ValueError(error)\n        if cwd.resolve() != Path(assignment[\"cwd\"]).resolve():\n            raise ValueError(f\"Assignment cwd changed for task {task.id}\")\n        return cwd\n\n\ndef release_completed_assignment(owner: str) -> bool:\n    \"\"\"Release a completed cwd lease only at a model turn boundary.\"\"\"\n    with task_lock:\n        assignment = teammate_assignments.get(owner)\n        if not assignment:\n            return False\n        task = load_task(str(assignment[\"task_id\"]))\n        if task.status != \"completed\" or task.owner != owner:\n            return False\n        teammate_assignments.pop(owner, None)\n        advance_assignment_version(owner)\n        if owner in globals().get(\"plan_gates\", {}):\n            globals()[\"plan_gates\"][owner] = \"not_required\"\n        return True\n","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s13_agent_teams/code.py#L391-L427","documentation":"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.","triggerScenarios":"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.","commonSituations":"Cleanup scripts that wipe .worktrees while agents hold assignments; version upgrades that alter worktree naming; workspace relocation under a changing symlink.","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."],"exampleFix":"# before\ndef owner_cwd(owner):\n    return cwd_for(owner)  # propagates ValueError\n\n# after\ndef owner_cwd(owner):\n    try:\n        return cwd_for(owner)\n    except ValueError:\n        with task_lock:\n            teammate_assignments.pop(owner, None)\n        return cwd_for(owner)  # re-derives a fresh lease","handlingStrategy":"fallback","validationCode":"from pathlib import Path\n\ndef lease_cwd_matches(owner: str) -> bool:\n    with task_lock:\n        a = teammate_assignments.get(owner) or {}\n        try:\n            t = load_task(str(a.get('task_id', '')))\n            cwd, err = task_worktree_cwd(t)\n        except (ValueError, TypeError):\n            return False\n        return not err and cwd.resolve() == Path(a.get('cwd', '/nonexistent')).resolve()","typeGuard":null,"tryCatchPattern":"try:\n    cwd = owner_cwd(owner)\nexcept ValueError as exc:\n    if 'Assignment cwd changed' in str(exc):\n        with task_lock:\n            teammate_assignments.pop(owner, None)\n        return owner_cwd(owner)  # one re-derivation; if it fails again, surface it\n    raise","preventionTips":["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."],"tags":["agent-coordination","stale-state","git-worktree","assignments"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}