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

.memlog.md frontmatter is not terminated

Error message

.memlog.md frontmatter is not terminated

What it means

After confirming the opening `---`, `split` searches for the next line that is exactly `---`. If none exists before EOF, the frontmatter is unterminated and the body cannot be located, so it raises. The closing fence must be a full line of exactly three dashes; a fence with trailing characters or indentation is not recognised, which is deliberate so that `---` inside a free-text topic/goal value cannot truncate the header.

Source

Thrown at src/scripts/memlog.py:101


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:
    """Stamp `updated` and keep it last so the field order stays predictable."""
    meta.pop("updated", None)
    meta["updated"] = now()

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Add a line that is exactly `---` after the last frontmatter field.
  2. Strip trailing whitespace from the intended closing-fence line.
  3. If a frontmatter value legitimately contains `---`, that is fine — just ensure a real closing fence exists later; the parser takes the first exact match.
  4. Re-initialise with `memlog init` if the file is unrecoverable.

Example fix

# before (.memlog.md)
---
topic: demo
goal: show the fix
- (note) hello   # no closing fence

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

- (note) hello
Defensive patterns

Strategy: validation

Validate before calling

lines = text.splitlines()
assert lines[0] == '---'
assert any(ln == '---' for ln in lines[1:]), '.memlog.md frontmatter is not terminated'

Type guard

def frontmatter_is_terminated(text: str) -> bool:
    lines = text.splitlines()
    return bool(lines) and lines[0] == '---' and any(ln == '---' for ln in lines[1:])

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: A file with an opening fence but no closing one (truncated write, partial paste); the closing line has trailing spaces, an indented fence, or four dashes; a frontmatter value contains a line that looks like a fence but the real closer was deleted.

Common situations: An interrupted write that left the file half-finished; an editor that 'helpfully' reformatted the fence; a manual edit that removed the closing line.

Related errors


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