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

{field} mismatch after write

Error message

{field} mismatch after write

What it means

Companion to the development_status check: after the atomic write and re-read, the scalar header fields `generated`, `last_updated`, and `project` must match `str(doc[field])`. It fires when YAML coerces one of those scalars on re-read so its `str()` differs from the written value — most often because the field looks like a date or number and YAML returns a `datetime.date`/`int` object whose `str()` is not byte-identical to the original string. The exception handler restores the original file (or deletes it) and fails loud, so the file is never left half-mutated.

Source

Thrown at src/bmm-skills/plan/bmad-sprint-planning/scripts/sprint_plan.py:457

        "warnings": warnings,
        **report,
    }

    if args.dry_run:
        print(json.dumps(result, default=str))
        return

    try:
        payload = _dump_bytes(yaml, doc)
        _atomic_write(args.status_file, payload, original_mode)
        verify_yaml = _make_yaml()
        with io.open(args.status_file, "r", encoding="utf-8") as fh:
            reread = verify_yaml.load(fh)
        if dict(reread.get("development_status") or {}) != {k: v for k, v in dev.items()}:
            raise ValueError("development_status mismatch after write")
        for field in ("generated", "last_updated", "project"):
            if str(reread.get(field)) != str(doc[field]):
                raise ValueError(f"{field} mismatch after write")
    except Exception as exc:
        if original_bytes is not None:
            try:
                _atomic_write(args.status_file, original_bytes, original_mode)
                restored = True
            except Exception:
                restored = False
        else:
            Path(args.status_file).unlink(missing_ok=True)
            restored = True
        _fail(f"write or validation failed, original {'restored' if restored else 'NOT restored'}: {exc}",
              restored=restored)
    print(json.dumps(result, default=str))


def _parse_stamp(value):
    from datetime import datetime

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Confirm `--date`, `--project`, and any inherited `generated` value are plain strings and not bare numbers or full datetimes.
  2. If `generated` carries a time component from an older file, normalise it to a `YYYY-MM-DD` string at the source.
  3. Force the emitter to quote these three fields as explicit strings (a custom representer or `default_style='"'`) so the loader cannot coerce them.
  4. Reproduce with `--dry-run` (which skips the write) and then read the file back by hand to see which field's re-read type diverges.

Example fix

# before: last_updated stored as a bare date, loader returns datetime.date
last_updated: 2026-08-12

# after: quote it so it stays a string
last_updated: "2026-08-12"

# or in code, coerce before writing:
doc[field] = str(doc[field])
Defensive patterns

Strategy: validation

Validate before calling

def scalar_roundtrips(value, yaml_lib) -> bool:
    dumped = yaml_lib.dump(str(value))
    back = yaml_lib.load(dumped)
    return str(back) == str(value)

fields = {f: doc[f] for f in ("generated","last_updated","project")}
bad = [f for f,v in fields.items() if not scalar_roundtrips(v, yaml)]
if bad:
    raise SystemExit(f"fields would not round-trip: {bad}")

Type guard

def is_iso_date_str(s: object) -> bool:
    if not isinstance(s, str): return False
    try:
        datetime.strptime(s, "%Y-%m-%d"); return True
    except ValueError:
        return False

Try / catch

# Treat non-zero exit as fatal; the handler has already restored the file.
import subprocess
res = subprocess.run(["python","sprint_plan.py",...], capture_output=True)
if res.returncode != 0:
    # message reports restoration status; do NOT retry the same inputs
    raise RuntimeError(res.stderr.decode())

Prevention

When it happens

Trigger: `generated`/`last_updated` is written as an ISO date string that the YAML loader turns into a `datetime.date`; `project` is a bare number or a YAML-reserved word. The check `str(reread.get(field)) != str(doc[field])` then trips because the re-read object's `str()` (e.g. `'2026-08-12'` from a date vs `'2026-08-12'` string is usually equal, but a `datetime` gives `'2026-08-12 00:00:00'`).

Common situations: Passing `--date 2026-08-12` where the loader returns a `datetime` whose repr adds time components; a project name that is purely numeric; a version bump of the YAML library that newly date-parses a previously-opaque string.

Related errors


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