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 _task_path() when a task ID fails the strict format TASK_ID_PATTERN = ^task_[0-9a-f]{8}$ (the literal prefix 'task_' plus exactly 8 lowercase hex characters, as generated by secrets.token_hex(4)). This is the single gatekeeper every task-file path passes through, so any API taking a task ID (load_task, get_task, can_start, etc.) rejects malformed IDs before touching the filesystem. It also fires for non-string input such as None or ints.
Source
Thrown at s13_agent_teams/code.py:125
finally:
if team is not None:
team.release()
@dataclass
class Task:
id: str
subject: str
description: str
status: str # pending | in_progress | completed
owner: str | None
blockedBy: list[str]
worktree: str | None = None
def _task_path(task_id: str) -> Path:
if not isinstance(task_id, str) or not TASK_ID_PATTERN.fullmatch(task_id):
raise ValueError(f"Invalid task ID: {task_id!r}")
path = (TASKS_DIR / f"{task_id}.json").resolve()
if (not TASKS_ROOT.is_relative_to(WORKDIR.resolve())
or not path.is_relative_to(TASKS_ROOT)):
raise ValueError(f"Invalid task ID: {task_id!r}")
return path
def create_task(subject: str, description: str = "",
blockedBy: list[str] | None = None) -> Task:
subject = subject.strip()
if not subject:
raise ValueError("Task subject cannot be empty")
dependencies = list(dict.fromkeys(blockedBy or []))
with task_store_lock():
for dependency in dependencies:
if not _task_path(dependency).is_file():
raise ValueError(f"Dependency not found: {dependency}")
for _ in range(100):View on GitHub (pinned to 985456f4ad)
Solutions
- Use IDs returned by create_task() verbatim; they are always task_ + 8 hex chars.
- If accepting external input, validate with the same regex ^task_[0-9a-f]{8}$ before calling task APIs.
- Strip whitespace and lowercase the ID before validation, then re-check the pattern.
- If you changed the ID scheme, keep TASK_ID_PATTERN in sync with the generator (secrets.token_hex length).
Example fix
// before
task = load_task(user_input) # ValueError if user passes 'Task_AB12CD34'
// after
import re
TASK_ID_RE = re.compile(r'^task_[0-9a-f]{8}$')
task_id = user_input.strip().lower()
if not TASK_ID_RE.fullmatch(task_id):
raise ValueError(f'Bad task id from user: {user_input!r}')
task = load_task(task_id) Defensive patterns
Strategy: validation
Validate before calling
import re
def is_valid_task_id(task_id) -> bool:
return isinstance(task_id, str) and re.fullmatch(r'task_[0-9a-f]{8}', task_id) is not None
# before: load_task(user_id)
if is_valid_task_id(user_id):
task = load_task(user_id) Type guard
import re
from typing import TypeGuard
_TASK_ID_RE = re.compile(r'^task_[0-9a-f]{8}$')
def is_task_id(value: object) -> TypeGuard[str]:
return isinstance(value, str) and bool(_TASK_ID_RE.fullmatch(value)) Try / catch
try:
task = load_task(task_id)
except ValueError as exc:
if 'Invalid task ID' in str(exc):
logger.warning('rejecting malformed task id %r', task_id)
return None
raise Prevention
- Treat task IDs as opaque tokens returned by create_task(); never construct them by hand.
- Validate external input against ^task_[0-9a-f]{8}$ at the API boundary.
- Lowercase and strip task IDs from user input before validation.
When it happens
Trigger: Calling load_task('123'), load_task('task_ABCDEFGH') (uppercase), load_task('task_abc') (too short), load_task('task_deadbeefg') (9 chars), or load_task(None). Any ID not exactly matching task_ + 8 lowercase hex chars raises ValueError.
Common situations: Passing a database-style integer ID, a user-typed slug, an ID from a different system, or a copy-pasted ID with whitespace/typo. Also truncation or case-folding of generated IDs in transit.
Related errors
- Invalid task ID: {task_id!r}
- Task subject cannot be empty
- Task subject cannot be empty
- Max 20 todos allowed
- Item {item_id}: text required
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/98188c2af20f1fd6.
Report an issue: GitHub.