shareAI-lab/learn-claude-code · error · ValueError
Invalid task status: {task.status}
Error message
Invalid task status: {task.status} What it means
Raised by TaskStore.load() in s10_task_system/code.py:143 when a task file's status field is anything other than 'pending', 'in_progress', or 'completed'. These three strings are the task lifecycle's closed vocabulary; the check runs after the ID-match check so the file is known to be well-located but its content is stale or hand-edited. Any other value — including old vocabulary like 'done', 'blocked', capitalized variants, or null — is rejected.
Source
Thrown at s10_task_system/code.py:143
json.dump(asdict(task), handle, indent=2)
return task
except FileExistsError:
continue
raise RuntimeError("Could not allocate a unique task ID")
def save(self, task: Task) -> None:
self._path(task.id, create_root=True).write_text(
json.dumps(asdict(task), indent=2),
encoding="utf-8",
)
def load(self, task_id: str) -> Task:
data = json.loads(self._path(task_id).read_text(encoding="utf-8"))
task = Task(**data)
if task.id != task_id:
raise ValueError(f"Task file ID does not match {task_id}")
if task.status not in ("pending", "in_progress", "completed"):
raise ValueError(f"Invalid task status: {task.status}")
return task
def list(self) -> list[Task]:
if not self.directory.exists():
return []
root = self._root()
return [self.load(path.stem)
for path in sorted(root.glob("task_*.json"))]
TASKS = TaskStore(TASKS_DIR)
def create_task(subject: str, description: str = "",
blockedBy: list[str] | None = None) -> Task:
return TASKS.create(subject, description, blockedBy)
View on GitHub (pinned to 985456f4ad)
Solutions
- Map legacy statuses onto the allowed set when loading: 'done'/'finished' -> 'completed', 'active'/'running' -> 'in_progress', anything else -> 'pending', then save(task) to persist the corrected value.
- Write status changes only through TaskStore.save() and the provided transition helpers so vocabulary stays closed.
- If a new status is genuinely needed, extend the tuple in load() and the creation code together, and migrate existing files.
Example fix
# before
data['status'] = 'done'
# after
STATUS_MAP = {'done': 'completed', 'finished': 'completed',
'active': 'in_progress', 'running': 'in_progress'}
data['status'] = STATUS_MAP.get(data['status'], 'pending') Defensive patterns
Strategy: fallback
Validate before calling
ALLOWED = {'pending', 'in_progress', 'completed'}
def status_is_loadable(status) -> bool:
return status in ALLOWED Type guard
def is_task_status(value) -> bool:
return value in ('pending', 'in_progress', 'completed') Try / catch
try:
task = store.load(task_id)
except ValueError as e:
if 'Invalid task status' in str(e):
legacy = {'done': 'completed', 'active': 'in_progress'}
task = patch_status(store, task_id, legacy) # rewrite file, reload
else:
raise Prevention
- Update statuses only through TaskStore.save()/transition helpers.
- When migrating stores, normalize legacy status strings to the three allowed values.
- Add a store lint pass that validates every file with the same rules load() enforces.
When it happens
Trigger: Hand-editing .tasks/task_xxx.json and setting status to 'done'; loading a store written by an older/newer version that used a different status vocabulary; external scripts writing tasks with status 'blocked'; a JSON null or missing field surfacing as None. Task(**data) would already fail on a missing key with TypeError, so this raise specifically hits present-but-wrong values.
Common situations: Schema drift between versions of the task system; users 'finishing' tasks with a text editor; integration code that assumed an open-ended status enum.
Related errors
- Unknown memory type: {mem_type}
- Invalid task ID: {task_id!r}
- Task subject cannot be empty
- Dependency not found: {dependency}
- Task file ID does not match {task_id}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/a1f754f15895d50d.
Report an issue: GitHub.