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

  1. Use IDs returned by create_task() verbatim; they are always task_ + 8 hex chars.
  2. If accepting external input, validate with the same regex ^task_[0-9a-f]{8}$ before calling task APIs.
  3. Strip whitespace and lowercase the ID before validation, then re-check the pattern.
  4. 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

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


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/98188c2af20f1fd6. Report an issue: GitHub.