HKUDS/Vibe-Trading · error · ValueError

today must be an ISO date string or None, got {today!r}

Error message

today must be an ISO date string or None, got {today!r}

What it means

Raised by _parse_today in strategy_discovery/facade.py when the optional `today` argument is neither None nor a string (e.g. a date/datetime object passed directly). The facade accepts only an ISO date string or None (None means use date.today()). Passing any other type is rejected before date.fromisoformat is attempted.

Source

Thrown at agent/src/strategy_discovery/facade.py:113

        return None
    return value


def _is_int(value: Any) -> bool:
    """True for real integers only (bool is explicitly excluded)."""
    return isinstance(value, int) and not isinstance(value, bool)


def _parse_today(today: str | None) -> date:
    """Resolve the injected clock: ISO ``today`` string or the wall clock.

    Raises ``ValueError`` for a non-string or unparseable ``today`` so the
    caller can answer with an error envelope instead of a wrong date.
    """
    if today is None:
        return date.today()
    if not isinstance(today, str):
        raise ValueError(f"today must be an ISO date string or None, got {today!r}")
    return date.fromisoformat(today.strip())


def _decay_fields(row, today: date) -> dict[str, Any]:
    """Read-time decay verdict for one evidence row (plan D1/D2).

    Computed from the row's own ``date_ranges`` window end at query time —
    never persisted. ``staleness_days`` (from ``last_verified``) is reported
    alongside but NEVER gates: refreshing unchanged artifacts must not be
    able to keep a row "fresh" forever.
    """
    age = evidence_age_days(row.date_ranges, today)
    status = classify_decay(row.date_ranges, today)
    return {
        "decay_status": status,
        "evidence_age_days": age,
        "staleness_days": staleness_days(row.last_verified, today),
        "_decay_warning": decay_warning(status, age),

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Pass today=None to use the real current date
  2. Pass an ISO string like '2024-01-31' (date.isoformat() output)
  3. If you hold a date object, convert: today=d.isoformat()

Example fix

# before
facade.query_strategies(today=datetime(2024, 1, 31))
# after
facade.query_strategies(today='2024-01-31')
Defensive patterns

Strategy: validation

Validate before calling

from datetime import date
if today is not None and not isinstance(today, str):
    today = today.isoformat() if hasattr(today, 'isoformat') else None
assert today is None or isinstance(today, str)

Type guard

def is_iso_date_or_none(v) -> bool:
    if v is None: return True
    if not isinstance(v, str): return False
    try: date.fromisoformat(v.strip()); return True
    except ValueError: return False

Try / catch

try:
    facade.query_strategies(today=today)
except ValueError as e:
    if 'today must be' in str(e): logger.warning('bad today, retrying with None'); facade.query_strategies(today=None)
    else: raise

Prevention

When it happens

Trigger: Calling query_strategies(today=date(2024,1,1)) or get_strategy_evidence(today=datetime.now()) — passing a date/datetime object or an int instead of an ISO string like '2024-01-01' or None.

Common situations: Callers already hold a datetime/date object from another API and pass it through unconverted; or pass an int timestamp assuming the facade parses it. Note date.fromisoformat itself will still raise ValueError for malformed strings, which is not caught here.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/5f9ae35658c6d40e. Report an issue: GitHub.