shareAI-lab/learn-claude-code · error · ValueError
Invalid task ID: {task_id!r}
Error message
Invalid task ID: {task_id!r} What it means
Raised by TaskStore._path() in s10_task_system/code.py:90 when task_id is not a string or does not fully match TASK_ID_PATTERN, r'^task_[0-9a-f]{8}$' — exactly the literal prefix 'task_' followed by 8 lowercase hexadecimal characters. All store APIs (exists, load, save, delete, transition helpers) build the file path through this gate, so any malformed ID is rejected before touching the filesystem. The {!r} in the message shows the offending value with its Python repr.
Source
Thrown at s10_task_system/code.py:90
owner: str | None
blockedBy: list[str]
class TaskStore:
def __init__(self, directory: Path):
self.directory = directory
def _root(self, create: bool = False) -> Path:
if create:
self.directory.mkdir(parents=True, exist_ok=True)
root = self.directory.resolve()
if not root.is_relative_to(WORKDIR.resolve()):
raise ValueError("Task store escapes the workspace")
return root
def _path(self, task_id: str, create_root: bool = False) -> Path:
if not isinstance(task_id, str) or not TASK_ID_PATTERN.fullmatch(task_id):
raise ValueError(f"Invalid task ID: {task_id!r}")
root = self._root(create=create_root)
path = (root / f"{task_id}.json").resolve()
if not path.is_relative_to(root):
raise ValueError(f"Invalid task ID: {task_id!r}")
return path
def exists(self, task_id: str) -> bool:
return self._path(task_id).is_file()
def create(self, subject: str, description: str = "",
blocked_by: list[str] | None = None) -> Task:
subject = subject.strip()
if not subject:
raise ValueError("Task subject cannot be empty")
dependencies = list(dict.fromkeys(blocked_by or []))
for dependency in dependencies:
if not self.exists(dependency):View on GitHub (pinned to 985456f4ad)
Solutions
- Only use IDs returned by TaskStore.create(), which are always 'task_' + 8 lowercase hex chars.
- Normalize before calling: strip whitespace and lowercase the hex portion, then re-check the pattern.
- If you import external IDs, maintain a mapping table to freshly created task_ IDs instead of forcing foreign formats through the API.
Example fix
# before
store.load('TASK_ab12cd34')
# after
import re
TASK_ID = re.compile(r'^task_[0-9a-f]{8}$')
tid = task_id.strip().lower()
if isinstance(task_id, str) and TASK_ID.fullmatch(tid):
task = store.load(tid)
else:
raise ValueError(f'not a valid task id: {task_id!r}') Defensive patterns
Strategy: type-guard
Validate before calling
import re
from s10_task_system.code import TASK_ID_PATTERN
def valid_task_id(task_id) -> bool:
return isinstance(task_id, str) and bool(TASK_ID_PATTERN.fullmatch(task_id)) Type guard
from typing import Any
import re
_TASK_ID = re.compile(r'^task_[0-9a-f]{8}$')
def is_task_id(value: Any) -> bool:
return isinstance(value, str) and bool(_TASK_ID.fullmatch(value)) Try / catch
try:
task = store.load(task_id)
except ValueError as e:
if 'Invalid task ID' in str(e):
return None # treat as not-found; log the repr for debugging
raise Prevention
- Only use IDs returned by create(); never hand-compose them.
- Normalize incoming IDs: value.strip().lower() before pattern-checking.
- Don't mix ID vocabularies (task_ vs bg_ vs cron_) across subsystems.
When it happens
Trigger: Calling store.load('42'), store.load('task_ABCD1234') (uppercase hex), 'task_12345' (too short), 'task_12345678g' (non-hex 'g'), a value with whitespace or a path suffix, or a non-string like None or an int. IDs originate from secrets.token_hex(4) at creation, so hand-typed or externally imported IDs are the usual offenders.
Common situations: Hardcoding IDs from another store or an older format; passing a UI-provided or LLM-provided identifier without normalization; copy/paste typos including the leading/trailing spaces or uppercase; feeding a bg_ background-task ID (s11) into the task store by mistake.
Related errors
- Task subject cannot be empty
- Dependency not found: {dependency}
- Could not allocate a unique task ID
- Task file ID does not match {task_id}
- Invalid task status: {task.status}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/e46f0c03e7e2dccf.
Report an issue: GitHub.