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

unparseable date: {raw!r} (want YYYY[-MM[-DD]])

Error message

unparseable date: {raw!r} (want YYYY[-MM[-DD]])

What it means

recon_kit.py's `parse_date` accepts only three formats — `YYYY-MM-DD`, `YYYY-MM`, or `YYYY` — tried in that order via `datetime.strptime`. If none parse, it raises this error naming the offending input. The function feeds the staleness command, where it parses both the optional `--today` argument and every claim's `pub_date`, so the message can surface from either a CLI flag or a record field.

Source

Thrown at src/core-skills/bmad-deep-recon/scripts/recon_kit.py:164

        claims[status] = claims.get(status, 0) + 1
    return out({
        "entries": entries,
        "by_type": dict(sorted(by_type.items())),
        "claims": dict(sorted(claims.items())),
        "claims_total": sum(claims.values()),
    }, 0)


# --- staleness ---------------------------------------------------------------

def parse_date(raw: str) -> date:
    raw = raw.strip()
    for fmt in ("%Y-%m-%d", "%Y-%m", "%Y"):
        try:
            return datetime.strptime(raw, fmt).date()
        except ValueError:
            continue
    raise ValueError(f"unparseable date: {raw!r} (want YYYY[-MM[-DD]])")


def add_months(d: date, months: int) -> date:
    total = d.month - 1 + months
    year, month = d.year + total // 12, total % 12 + 1
    return date(year, month, min(d.day, calendar.monthrange(year, month)[1]))


def cmd_staleness(args) -> int:
    try:
        payload = json.loads(read_text(args.file))
        windows = {k.lower(): int(v) for k, v in json.loads(args.windows).items()}
        today = parse_date(args.today) if args.today else date.today()
    except (ValueError, json.JSONDecodeError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    claims = payload["claims"] if isinstance(payload, dict) else payload
    results, no_window, stale_count = [], set(), 0

View on GitHub (pinned to b70486b9bd)

Solutions

  1. Normalise the input to one of the three accepted forms; prefer `YYYY-MM-DD`.
  2. If the value comes from `--today`, pass an explicit ISO date or omit the flag (it defaults to `date.today()`).
  3. Pre-process claim records to canonicalise pub_date before feeding them to `staleness` (e.g. `dateutil.parser.parse(...).strftime('%Y-%m-%d')`).
  4. Filter or flag records whose pub_date is empty or non-ISO before running the command.

Example fix

# before
--today 12/08/2026
# claim pub_date: "Aug 12, 2026"

# after
--today 2026-08-12
# claim pub_date: "2026-08-12"
Defensive patterns

Strategy: validation

Validate before calling

from datetime import datetime
def is_supported_date(s: str) -> bool:
    for fmt in ("%Y-%m-%d","%Y-%m","%Y"):
        try: datetime.strptime(s.strip(), fmt); return True
        except ValueError: continue
    return False

Type guard

def is_iso_date(s: object) -> bool:
    if not isinstance(s, str): return False
    return any(_matches(s.strip(), f) for f in ("%Y-%m-%d","%Y-%m","%Y"))

Try / catch

try:
    today = parse_date(args.today) if args.today else date.today()
except ValueError as e:
    print(f"error: {e}", file=sys.stderr); return 2

Prevention

When it happens

Trigger: Passing `--today 12/08/2026` (slashes, day-first), a `pub_date` of `"Q3 2026"`, `"Aug 2026"`, `"2026.08.12"`, an empty string, or a two-digit year. Also a full datetime `2026-08-12T14:00` which has no matching format.

Common situations: Locale-formatted dates copied from a browser or spreadsheet (MM/DD/YYYY); free-text publication dates scraped from sources that don't use ISO; a missing pub_date stored as the empty string.

Related errors


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