bmad-code-org/BMAD-METHOD · error · RenderError

{label} must resolve to an absolute path: {resolved}

Error message

{label} must resolve to an absolute path: {resolved}

What it means

In _resolve_config_value, a config string containing the literal {project-root} is substituted with project_root and the result must be an absolute path. This fires when the substituted string is not absolute -- typically because of leading whitespace or a relative prefix before {project-root} in the TOML value, which makes Path(...).is_absolute() False despite the embedded absolute project root.

Source

Thrown at src/scripts/render_skill.py:124

            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():
        path = f"{prefix}.{name}" if prefix else name
        if name == key and not isinstance(value, (dict, list)):
            matches.append((path, value))
        matches.extend(_find_config_values(value, key, path))
    return matches


def _resolve_short_config(
    central: dict[str, Any], key: str, project_root: Path
) -> tuple[str, str]:

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Ensure the config value starts with {project-root} with no leading whitespace or relative prefix.
  2. Trim stray whitespace around the value in the TOML file.
  3. Use a literal absolute path if {project-root} is not needed at the start of the string.

Example fix

# before
[paths]
output = " {project-root}/_bmad/render"

# after
[paths]
output = "{project-root}/_bmad/render"
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def assert_absolute_after_substitution(text: str, project_root: Path) -> None:
    if "{project-root}" in text:
        resolved = text.replace("{project-root}", str(project_root))
        if not Path(resolved).is_absolute():
            raise SystemExit(f"value does not resolve to absolute path: {resolved!r}")

Prevention

When it happens

Trigger: A config value like output = " {project-root}/_bmad/render" (leading space) or output = "rel/{project-root}" (relative prefix) -- the leading text makes the whole path relative even though an absolute segment is embedded. Path(resolved).is_absolute() returns False.

Common situations: Accidental leading/trailing whitespace in a TOML string; a relative segment accidentally prepended; copy-pasting a path template.

Related errors


AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13). Data as JSON: /api/errors/7f7f57e73a859491. Report an issue: GitHub.