{"record":{"id":"5abe709ce68141fa","repo":"can1357/oh-my-pi","slug":"todo-phases-toml-task-kind-r-must-be-a-list-of","errorCode":null,"errorMessage":"todo_phases.toml[{task_kind!r}] must be a list of phases","messagePattern":"todo_phases\\.toml\\[(.+?)\\] must be a list of phases","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/robomp/src/persona.py","lineNumber":75,"sourceCode":"    return data\n\n\ndef _require_mapping(value: Any, context: str) -> Mapping[str, Any]:\n    if not isinstance(value, Mapping):\n        raise ValueError(f\"{context} must be a table\")\n    return value\n\n\ndef _require_nonempty_str(value: Any, context: str) -> str:\n    if not isinstance(value, str) or not value.strip():\n        raise ValueError(f\"{context} must be a non-empty string\")\n    return value\n\n\ndef seed_phases(task_kind: str) -> list[dict[str, Any]]:\n    raw_phases = _load_toml(\"todo_phases.toml\").get(task_kind, [])\n    if not isinstance(raw_phases, list):\n        raise ValueError(f\"todo_phases.toml[{task_kind!r}] must be a list of phases\")\n\n    phases: list[dict[str, Any]] = []\n    for phase_index, raw_phase in enumerate(raw_phases):\n        phase = _require_mapping(raw_phase, f\"todo_phases.toml[{task_kind!r}][{phase_index}]\")\n        name = _require_nonempty_str(\n            phase.get(\"name\"),\n            f\"todo_phases.toml[{task_kind!r}][{phase_index}].name\",\n        )\n        raw_tasks = phase.get(\"tasks\")\n        if not isinstance(raw_tasks, list) or not raw_tasks:\n            raise ValueError(f\"todo_phases.toml[{task_kind!r}][{phase_index}].tasks must be a non-empty list\")\n        tasks = [\n            _require_nonempty_str(\n                task,\n                f\"todo_phases.toml[{task_kind!r}][{phase_index}].tasks[{task_index}]\",\n            )\n            for task_index, task in enumerate(raw_tasks)\n        ]","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/persona.py#L57-L93","documentation":"`seed_phases(task_kind)` loads `todo_phases.toml`, reads the `task_kind` key, and requires its value to be a list of phase tables. When the value under that key exists but is not a list (string, integer, or bare table), it raises ValueError with the offending key quoted. A missing key returns [] without error — only a present-but-wrong-typed value triggers this message.","triggerScenarios":"Calling `seed_phases('bug')`, `seed_phases('question')`, etc., when `todo_phases.toml` defines `bug = \"reproduce then fix\"`, `bug = 3`, or `[todo_phases.bug]` (a bare table) instead of an array of phase tables such as `bug = [{ name = ..., tasks = [...] }]` or `[[todo_phases.bug]]` blocks.","commonSituations":"A hand-edit or bad merge replacing the array with a scalar; a YAML/JSON-style paste where list brackets were lost; using `[todo_phases.bug]` table syntax (raises) instead of `[[todo_phases.bug]]` array-of-tables; seeing this error means the key exists — a typo'd task_kind would silently return [] instead.","solutions":["Confirm the value type: `python -c \"import tomllib; print(type(tomllib.load(open('todo_phases.toml','rb'))['bug']))\"` must be list","Rewrite the entry as an array of tables: `bug = [{ name = \"reproduce\", tasks = [\"...\", \"...\"] }, ...]` or `[[todo_phases.bug]]` blocks","Restore the original file if a merge broke it: `git checkout -- python/robomp/src/prompts/todo_phases.toml`","Restart the process after fixing — `_load_toml` results are `@cache`d for the process lifetime"],"exampleFix":"# before (todo_phases.toml)\n[todo_phases]\nbug = \"reproduce then fix\"\n\n# after\n[todo_phases]\nbug = [{ name = \"reproduce\", tasks = [\"write failing repro\", \"confirm failure\"] }, { name = \"fix\", tasks = [\"patch\", \"run tests\"] }]","handlingStrategy":"validation","validationCode":"import tomllib\nphases = tomllib.load(open(\"todo_phases.toml\", \"rb\")).get(\"bug\")\nif phases is not None:\n    assert isinstance(phases, list), \"todo_phases[bug] must be a list\"\n    for i, ph in enumerate(phases):\n        assert isinstance(ph, dict) and ph.get(\"name\") and ph.get(\"tasks\"), f\"phase {i} malformed\"","typeGuard":"def is_phase_list(value) -> bool:\n    return isinstance(value, list) and all(isinstance(p, dict) for p in value)","tryCatchPattern":"try:\n    phases = seed_phases(task_kind)\nexcept ValueError as e:\n    logger.error(\"todo_phases.toml malformed for %s: %s\", task_kind, e)\n    raise  # or degrade to [] if the workflow tolerates no seeded phases","preventionTips":["Write phase lists with `[[todo_phases.<kind>]]` array-of-tables syntax so the type is unambiguous","Remember a missing task_kind key is legal (returns []); wrong type is what raises — audit key spellings separately","Validate todo_phases.toml (every value a list of tables with non-empty name/tasks) at service startup","Add a regression test calling seed_phases for every task_kind used in the codebase"],"tags":["toml","configuration","type-guard","validation"],"backgroundTag":"toml-schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}