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(), 0View on GitHub (pinned to b70486b9bd)
Solutions
- Normalise the input to one of the three accepted forms; prefer `YYYY-MM-DD`.
- If the value comes from `--today`, pass an explicit ISO date or omit the flag (it defaults to `date.today()`).
- Pre-process claim records to canonicalise pub_date before feeding them to `staleness` (e.g. `dateutil.parser.parse(...).strftime('%Y-%m-%d')`).
- 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
- Normalise all dates to YYYY-MM-DD at the source.
- Pre-canonicalise scraped pub_dates with dateutil before feeding staleness.
- Omit --today to use the default (date.today()) when in doubt.
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
- --extra must be a JSON array of objects
- each --extra entry must be a JSON object, got: {item!r}
- --extra must be a JSON array of objects
- each --extra entry must be a JSON object, got: {item!r}
- resolveChannel: pinned channel requires a pin value
AI-assisted analysis of bmad-code-org/BMAD-METHOD@b70486b9bd (2026-08-13).
Data as JSON: /api/errors/0f3554a1720fb368.
Report an issue: GitHub.