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

.memlog.md has no frontmatter

Error message

.memlog.md has no frontmatter

What it means

memlog.py's `split` parses a `.memlog.md` file into frontmatter and body. It requires the very first line to be exactly `---`. This error means the file does not start with an opening fence — either the file is empty, the first line has trailing whitespace or a different dash count, or the file is a plain markdown body with no frontmatter block at all. The contract is documented at the top of the script: every memlog begins with a `---`-delimited header.

Source

Thrown at src/scripts/memlog.py:98

def now() -> str:
    return datetime.now().strftime("%Y-%m-%dT%H:%M")


def resolve(args) -> Path:
    """The memlog file, from either addressing mode: {workspace}/.memlog.md or an explicit --path."""
    return Path(args.path) if args.path else Path(args.workspace) / MEMLOG


def split(text: str) -> tuple[dict, str]:
    """Return (frontmatter dict in source order, body str). Frontmatter is plain key: value.

    The closing fence is the first line that is *exactly* `---`, so a `---` inside a
    field value (topic/goal are free user text) never truncates the frontmatter.
    """
    lines = text.splitlines()
    if not lines or lines[0] != "---":
        raise ValueError(".memlog.md has no frontmatter")
    end = next((i for i in range(1, len(lines)) if lines[i] == "---"), None)
    if end is None:
        raise ValueError(".memlog.md frontmatter is not terminated")
    meta: dict[str, str] = {}
    for line in lines[1:end]:
        if ":" in line:
            k, v = line.split(":", 1)
            meta[k.strip()] = v.strip()
    return meta, "\n".join(lines[end + 1:]).lstrip("\n")


def render(meta: dict, body: str) -> str:
    # Neutralize newlines in values so a multi-line field can't break the fence on re-read.
    fm = "\n".join(f"{k}: {' '.join(str(v).splitlines())}" for k, v in meta.items())
    return "---\n" + fm + "\n---\n\n" + body.rstrip("\n") + "\n"


def touch(meta: dict) -> None:

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Prepend a proper frontmatter block starting with a line that is exactly three dashes.
  2. Use `memlog init --workspace <dir>` to create a correctly shaped file rather than hand-writing it.
  3. Check for trailing whitespace or CRLF on the first line and strip it.
  4. If the file should have no frontmatter, reconsider: memlog requires it; convert the body into entries via `append`.

Example fix

# before (.memlog.md)
- (note) hello

# after
---
topic: demo
goal: show the fix
updated: 2026-08-12T10:00
---

- (note) hello
Defensive patterns

Strategy: validation

Validate before calling

text = Path('.memlog.md').read_text()
lines = text.splitlines()
assert lines and lines[0] == '---', '.memlog.md has no frontmatter'

Type guard

def has_opening_fence(text: str) -> bool:
    lines = text.splitlines()
    return bool(lines) and lines[0] == '---'

Try / catch

from memlog import split
try:
    meta, body = split(text)
except ValueError as e:
    print(f"error: {e}", file=sys.stderr); sys.exit(2)

Prevention

When it happens

Trigger: Resuming a session against a file that was hand-created without the fence; a file whose first line is `--- ` (trailing space) or `----` (four dashes); an empty file; a body-only log saved by an external editor that stripped the frontmatter.

Common situations: Manually seeding a memlog instead of using `init`; a find-replace that altered the fence; a sync tool that normalised line endings and broke the exact-match.

Related errors


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