{"record":{"id":"3c277cb194fdd6e5","repo":"ultraworkers/claw-code","slug":"board-validation-failed-n-errors","errorCode":null,"errorMessage":"board validation failed:\\n{errors}","messagePattern":"board validation failed:\\\\n(.+?)","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"scripts/generate_cc2_board.py","lineNumber":495,"sourceCode":"            \"roadmap_headings_total\": len(headings),\n            \"roadmap_headings_mapped\": len(mapped_heading_lines),\n            \"unmapped_roadmap_heading_lines\": unmapped_heading_lines,\n            \"duplicate_roadmap_heading_lines\": duplicate_heading_lines,\n            \"roadmap_actions_total\": len(actions),\n            \"roadmap_actions_mapped\": len([item for item in items if item.get(\"source_type\") == \"roadmap_action\"]),\n        },\n        \"summary\": {},\n        \"items\": items,\n    }\n    board[\"summary\"] = {\n        \"by_status\": summarize_counts(items, \"status\"),\n        \"by_release_bucket\": summarize_counts(items, \"release_bucket\"),\n        \"by_source_type\": summarize_counts(items, \"source_type\"),\n        \"by_owner_lane\": summarize_counts(items, \"owner_lane\"),\n    }\n    errors = validate_board(board)\n    if errors:\n        raise SystemExit(\"board validation failed:\\n\" + \"\\n\".join(errors))\n    return board\n\n\ndef main() -> int:\n    parser = argparse.ArgumentParser(description=__doc__)\n    parser.add_argument(\"--repo-root\", type=Path, default=Path.cwd())\n    parser.add_argument(\"--out-dir\", type=Path, default=None)\n    args = parser.parse_args()\n    repo_root = args.repo_root.resolve()\n    out_dir = args.out_dir or (repo_root / \".omx\" / \"cc2\")\n    try:\n        board = build_board(repo_root)\n    except FileNotFoundError as exc:\n        print(f\"error: {exc}\", file=sys.stderr)\n        return 1\n    out_dir.mkdir(parents=True, exist_ok=True)\n    board_json = out_dir / \"board.json\"\n    board_md = out_dir / \"board.md\"","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/b71afddae100ced324457337925a694686b8fef2/scripts/generate_cc2_board.py#L477-L513","documentation":"Raised at the end of build_board() (scripts/generate_cc2_board.py:493-495) as a SystemExit whose message is the newline-joined list returned by validate_board(). It is a self-check gate: after assembling every board item from ROADMAP.md headings/actions and .omx/research JSON, the generator asserts each item has all REQUIRED_ITEM_FIELDS, a unique id, a status in STATUSES, a release_bucket in RELEASE_BUCKETS, list-typed dependencies, and that roadmap heading coverage is complete and non-duplicated (headings_total == headings_mapped, no unmapped or duplicate heading lines). Any violation aborts generation before board.json/board.md are written.","triggerScenarios":"Editing the classification functions (status_for, release_bucket_for, dependencies_for) so a status like 'in_progress' or a bucket like 'beta' is produced that is not in the STATUSES/RELEASE_BUCKETS sets at scripts/generate_cc2_board.py:27-45. Research JSON entries missing the 'number' field: issue_item() then emits id 'CC2-ISSUE-CLAW-OPEN-LATEST-None' for every such issue, triggering 'duplicate id'. Adding a new item type that omits a REQUIRED_ITEM_FIELDS key (e.g. verification_required or deferral_rationale). Filtering/renaming headings in ROADMAP.md inconsistently with the heading-vs-item mapping, producing 'unmapped heading lines' or 'roadmap heading total/mapped mismatch'. Making dependencies a string instead of a list in a custom item builder.","commonSituations":"Extending the board with a new lifecycle state (e.g. splitting done_verify) while forgetting to register it in STATUSES. Curated research manifests from GraphQL/REST exports where some issues lack fields the script assumes. Hand-rolled item dicts in a fork that drift from the schema. ROADMAP.md edits (removed or duplicated headings) on a branch that the coverage accounting at scripts/generate_cc2_board.py:443-446 then flags. CI regenerating the board after upstream renames of the frozen plan/roadmap structure.","solutions":["Read the error body first: it names the exact item index/id and rule (e.g. 'CC2-RM-H0012 invalid status in_progress'), so fix the named item or the classifier branch that produced the bad value.","If you intentionally added a new status/bucket, add it to the STATUSES / RELEASE_BUCKETS sets at the top of the file AND to generation_policy.status_values/release_buckets so validation and the emitted policy stay consistent.","For 'duplicate id' on issue items, verify each entry in .omx/research/claw-open-latest.json and claw-issues.json has a unique 'number'; re-export the manifests or give issue_item() a fallback (e.g. hash of url) when number is None.","For coverage errors ('unmapped heading lines', 'total/mapped mismatch'), re-run without local edits to parse_roadmap/roadmap_item and confirm every heading still yields an item; the mapped set is items with source_type == 'roadmap_heading', so ensure your changes don't drop or relabel that field.","For 'missing fields' on custom items, copy the full key set from roadmap_item()/issue_item() (all 9 REQUIRED_ITEM_FIELDS) instead of building a partial dict.","Rerun `python3 scripts/generate_cc2_board.py` after each fix; validation re-executes on every run, so iterate until it writes .omx/cc2/board.json and board.md."],"exampleFix":"# before: classifier emits an unregistered status -> 'board validation failed: CC2-RM-H0007 invalid status in_progress'\ndef status_for(record):\n    ...\n    if \"wip\" in combined:\n        return \"in_progress\"  # not in STATUSES -> validation aborts\n\n# after: register the value in the schema sets, then use it\nSTATUSES = {\n    \"context\", \"active\", \"open\", \"done_verify\", \"stale_done\",\n    \"superseded\", \"deferred_with_rationale\", \"rejected_not_claw\",\n    \"in_progress\",  # added alongside every consumer (validate_board, render, policy)\n}\n\ndef status_for(record):\n    ...\n    if \"wip\" in combined:\n        return \"in_progress\"","handlingStrategy":"validation","validationCode":"import scripts.generate_cc2_board as gen\n\ndef items_pass_schema(items: list[dict]) -> list[str]:\n    \"\"\"Dry-run the same rules validate_board() enforces, before wiring items in.\"\"\"\n    problems: list[str] = []\n    seen: set = set()\n    for i, item in enumerate(items, 1):\n        missing = [f for f in gen.REQUIRED_ITEM_FIELDS if f not in item]\n        if missing:\n            problems.append(f\"item {i} missing fields: {missing}\")\n        if item.get(\"id\") in seen:\n            problems.append(f\"duplicate id: {item.get('id')}\")\n        seen.add(item.get(\"id\"))\n        if item.get(\"status\") not in gen.STATUSES:\n            problems.append(f\"{item.get('id')} invalid status {item.get('status')}\")\n        if item.get(\"release_bucket\") not in gen.RELEASE_BUCKETS:\n            problems.append(f\"{item.get('id')} invalid release_bucket {item.get('release_bucket')}\")\n        if not isinstance(item.get(\"dependencies\"), list):\n            problems.append(f\"{item.get('id')} dependencies must be list\")\n    return problems\n\n# in tests / CI before regenerating the board:\n# assert gen.validate_board(gen.build_board(repo_root)) == []","typeGuard":"from typing import Any\n\ndef is_valid_board_item(item: Any) -> bool:\n    required = {\n        \"id\", \"title\", \"source_anchor\", \"source_type\", \"release_bucket\",\n        \"status\", \"dependencies\", \"verification_required\", \"deferral_rationale\",\n    }\n    return (\n        isinstance(item, dict)\n        and required <= item.keys()\n        and isinstance(item.get(\"id\"), str)\n        and item.get(\"status\") in {\n            \"context\", \"active\", \"open\", \"done_verify\", \"stale_done\",\n            \"superseded\", \"deferred_with_rationale\", \"rejected_not_claw\",\n        }\n        and item.get(\"release_bucket\") in {\n            \"alpha_blocker\", \"beta_adoption\", \"ga_ecosystem\",\n            \"post_2_0_research\", \"rejected_not_claw\", \"context\", \"2.x_intake\",\n        }\n        and isinstance(item.get(\"dependencies\"), list)\n    )","tryCatchPattern":"import sys\nimport scripts.generate_cc2_board as gen\n\ntry:\n    board = gen.build_board(repo_root)\nexcept SystemExit as exc:\n    # exc.code carries the full 'board validation failed:\\n<item-level details>' message;\n    # parse item ids out of it and fail the CI step with per-item diagnostics\n    details = str(exc.code)\n    print(f\"board regeneration failed schema gate:\\n{details}\", file=sys.stderr)\n    sys.exit(1)","preventionTips":["Treat STATUSES, RELEASE_BUCKETS, and REQUIRED_ITEM_FIELDS as the schema contract: any new lifecycle value or item shape must be registered there in the same change that produces it.","Add a CI step that regenerates the board (python3 scripts/generate_cc2_board.py) on every ROADMAP.md and .omx/research change, so validation failures surface in the PR that caused them instead of at release time.","When adding custom item builders, copy the dict shape of roadmap_item()/issue_item() wholesale and assert is_valid_board_item(item) in a unit test rather than hand-writing keys.","Sanity-check research manifests before use: every issue entry needs a unique 'number', otherwise issue_item() mints colliding 'CC2-ISSUE-...-None' ids.","Never partially suppress coverage: if you filter headings from the board, adjust the coverage accounting in build_board() in the same edit so headings_total stays equal to headings_mapped."],"tags":["python","data-validation","schema","board-generation","developer-error","ci"],"backgroundTag":null,"analyzedSha":"b71afddae100ced324457337925a694686b8fef2","analyzedAt":"2026-08-16T02:39:29.677Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}