{"record":{"id":"b15af609c19c3ed1","repo":"bmad-code-org/BMAD-METHOD","slug":"render-entry-is-missing-skill-dir-workflow-md","errorCode":null,"errorMessage":"render entry is missing: {skill_dir / 'workflow.md'}","messagePattern":"render entry is missing: (.+?)","errorType":"exception","errorClass":"RenderError","httpStatus":null,"severity":"error","filePath":"src/scripts/render_skill.py","lineNumber":114,"sourceCode":"\n\ndef _load_sources(skill_dir: Path) -> dict[str, str]:\n    sources: dict[str, str] = {}\n    for candidate in sorted(skill_dir.rglob(\"*.md\")):\n        if candidate.name == \"SKILL.md\":\n            continue\n        name = candidate.relative_to(skill_dir).as_posix()\n        path = candidate.resolve(strict=True)\n        if not path.is_relative_to(skill_dir):\n            raise RenderError(f\"render source escapes skill directory: {name}\")\n        if not path.is_file():\n            raise RenderError(f\"render source is missing or not a file: {path}\")\n        try:\n            sources[name] = path.read_text(encoding=\"utf-8\")\n        except (OSError, UnicodeError) as error:\n            raise RenderError(f\"failed to read render source {path}: {error}\") from error\n    if \"workflow.md\" not in sources:\n        raise RenderError(f\"render entry is missing: {skill_dir / 'workflow.md'}\")\n    return sources\n\n\ndef _resolve_config_value(value: Any, label: str, project_root: Path) -> str:\n    text = _require_string(value, label)\n    if \"{project-root}\" not in text:\n        return text\n    resolved = text.replace(\"{project-root}\", str(project_root))\n    if not Path(resolved).is_absolute():\n        raise RenderError(f\"{label} must resolve to an absolute path: {resolved}\")\n    return resolved\n\n\ndef _find_config_values(data: Any, key: str, prefix: str = \"\") -> list[tuple[str, Any]]:\n    matches: list[tuple[str, Any]] = []\n    if not isinstance(data, dict):\n        return matches\n    for name, value in data.items():","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/src/scripts/render_skill.py#L96-L132","documentation":"After collecting sources, _load_sources requires a workflow.md entry -- it is the entry point returned by render() (destination / \"workflow.md\"). If no workflow.md exists at the skill root, rendering aborts because the snapshot would have no defined entry file.","triggerScenarios":"A skill directory containing .md files but none named workflow.md at its root; workflow.md is a symlink that escaped (rejected by the earlier guard and thus absent from sources); a case mismatch such as Workflow.md on a case-sensitive filesystem.","commonSituations":"New skill missing its workflow entry; renamed file; case-sensitivity mismatch between macOS (development) and Linux (CI).","solutions":["Create workflow.md at the root of the skill directory.","Verify exact lowercase case: workflow.md (not Workflow.md).","Ensure workflow.md is a real file, not an escaped symlink (see the escape guard)."],"exampleFix":"# before: only Workflow.md (wrong case) or workfow.md (typo) exists\n# after\ntouch skills/my-skill/workflow.md","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef assert_workflow_entry(skill_dir: Path) -> None:\n    entry = skill_dir / \"workflow.md\"\n    if not entry.is_file():\n        raise SystemExit(f\"missing required entry: {entry}\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Every skill must have a lowercase workflow.md at its root.","Watch for case mismatches between macOS dev and Linux CI.","Add workflow.md existence to a skill-template scaffold check."],"tags":["python","skill","validation","workflow","entry-point"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}