HKUDS/Vibe-Trading · error · ValueError

'failure_kind' must be 'dispatch', 'schedule', or null

Error message

'failure_kind' must be 'dispatch', 'schedule', or null

What it means

Raised by Job from_dict (agent/src/scheduled_research/models.py:457) when 'failure_kind' is present, non-null, and not one of the literals 'dispatch' or 'schedule'. It classifies whether the last failure happened in dispatching the run or computing the schedule; any other string (or non-string) is rejected.

Source

Thrown at agent/src/scheduled_research/models.py:457

        created_at = data["created_at"]
        if not isinstance(next_run_at, int) or not isinstance(created_at, int):
            raise TypeError("'next_run_at' and 'created_at' must be integers (epoch ms)")
        last_run_at = data.get("last_run_at")
        if last_run_at is not None and not isinstance(last_run_at, int):
            raise TypeError("'last_run_at' must be an integer (epoch ms) or null")
        consecutive_failures = data.get("consecutive_failures", 0)
        if (
            isinstance(consecutive_failures, bool)
            or not isinstance(consecutive_failures, int)
            or consecutive_failures < 0
        ):
            raise TypeError("'consecutive_failures' must be a non-negative integer")
        last_error = data.get("last_error")
        failure_kind = data.get("failure_kind")
        if last_error is not None and not isinstance(last_error, str):
            raise TypeError("'last_error' must be a string or null")
        if failure_kind is not None and failure_kind not in {"dispatch", "schedule"}:
            raise ValueError("'failure_kind' must be 'dispatch', 'schedule', or null")
        # Never raises: the store quarantines the whole file when a single
        # record fails to load, so an unusable timezone value degrades that
        # one job to UTC — the semantics it had before the field existed —
        # instead of taking every other job down with it. Absent, blank, and
        # non-string values all normalize to None.
        raw_tz = data.get("timezone")
        tz = raw_tz if isinstance(raw_tz, str) and raw_tz.strip() else None
        if raw_tz is not None and tz is None:
            logger.warning(
                "scheduled research job %s has an unusable timezone %r; "
                "evaluating its schedule in UTC",
                job_id,
                raw_tz,
            )
        status = JobStatus(data["status"])
        title = data.get("title", "")
        source_type = data.get("source_type", "prompt")
        playbook_slug = data.get("playbook_slug")

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Use exact lowercase literals 'dispatch' or 'schedule'
  2. Map new categories to one of these two or null until the enum is extended
  3. Normalize empty strings to null before persisting

Example fix

// before
record["failure_kind"] = kind.title()  # 'Dispatch'

// after
record["failure_kind"] = kind if kind in {"dispatch", "schedule"} else None
Defensive patterns

Strategy: validation

Validate before calling

VALID_KINDS = {"dispatch", "schedule"}

def failure_kind_ok(v):
    return v is None or (isinstance(v, str) and v in VALID_KINDS)

Type guard

VALID_KINDS = {"dispatch", "schedule"}

def safe_failure_kind(v):
    if isinstance(v, str):
        v = v.lower()
        if v in VALID_KINDS:
            return v
    return None

Try / catch

try:
    job = Job.from_dict(record)
except ValueError as exc:
    if "failure_kind" in str(exc):
        record = dict(record); record["failure_kind"] = None
        job = Job.from_dict(record)
    else:
        raise

Prevention

When it happens

Trigger: {"failure_kind": "Dispatch"} (wrong case), {"failure_kind": "timeout"}, or {"failure_kind": ""}. Only exactly 'dispatch', 'schedule', or null pass.

Common situations: Introducing a new failure category in one component before the models module knows it; case inconsistencies between writer and reader; empty string sent instead of null by form/JSON handling.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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