bmad-code-org/BMAD-METHOD · error · RenderError
render entry is missing: {skill_dir / 'workflow.md'}
Error message
render entry is missing: {skill_dir / 'workflow.md'} What it means
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.
Source
Thrown at src/scripts/render_skill.py:114
def _load_sources(skill_dir: Path) -> dict[str, str]:
sources: dict[str, str] = {}
for candidate in sorted(skill_dir.rglob("*.md")):
if candidate.name == "SKILL.md":
continue
name = candidate.relative_to(skill_dir).as_posix()
path = candidate.resolve(strict=True)
if not path.is_relative_to(skill_dir):
raise RenderError(f"render source escapes skill directory: {name}")
if not path.is_file():
raise RenderError(f"render source is missing or not a file: {path}")
try:
sources[name] = path.read_text(encoding="utf-8")
except (OSError, UnicodeError) as error:
raise RenderError(f"failed to read render source {path}: {error}") from error
if "workflow.md" not in sources:
raise RenderError(f"render entry is missing: {skill_dir / 'workflow.md'}")
return sources
def _resolve_config_value(value: Any, label: str, project_root: Path) -> str:
text = _require_string(value, label)
if "{project-root}" not in text:
return text
resolved = text.replace("{project-root}", str(project_root))
if not Path(resolved).is_absolute():
raise RenderError(f"{label} must resolve to an absolute path: {resolved}")
return resolved
def _find_config_values(data: Any, key: str, prefix: str = "") -> list[tuple[str, Any]]:
matches: list[tuple[str, Any]] = []
if not isinstance(data, dict):
return matches
for name, value in data.items():View on GitHub (pinned to b70486b9bd)
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).
Example fix
# before: only Workflow.md (wrong case) or workfow.md (typo) exists # after touch skills/my-skill/workflow.md
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def assert_workflow_entry(skill_dir: Path) -> None:
entry = skill_dir / "workflow.md"
if not entry.is_file():
raise SystemExit(f"missing required entry: {entry}") Prevention
- 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.
When it happens
Trigger: 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.
Common situations: New skill missing its workflow entry; renamed file; case-sensitivity mismatch between macOS (development) and Linux (CI).
Related errors
- render source is missing or not a file: {path}
- {label} must be a list of tables
- {item_label} must be a table
- duplicate review layer id `{identifier}`
- render source escapes skill directory: {name}
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/b15af609c19c3ed1.
Report an issue: GitHub.