{"record":{"id":"aee657744d2f6f02","repo":"HKUDS/Vibe-Trading","slug":"failure-kind-must-be-dispatch-schedule-or","errorCode":null,"errorMessage":"'failure_kind' must be 'dispatch', 'schedule', or null","messagePattern":"'failure_kind' must be 'dispatch', 'schedule', or null","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":457,"sourceCode":"        created_at = data[\"created_at\"]\n        if not isinstance(next_run_at, int) or not isinstance(created_at, int):\n            raise TypeError(\"'next_run_at' and 'created_at' must be integers (epoch ms)\")\n        last_run_at = data.get(\"last_run_at\")\n        if last_run_at is not None and not isinstance(last_run_at, int):\n            raise TypeError(\"'last_run_at' must be an integer (epoch ms) or null\")\n        consecutive_failures = data.get(\"consecutive_failures\", 0)\n        if (\n            isinstance(consecutive_failures, bool)\n            or not isinstance(consecutive_failures, int)\n            or consecutive_failures < 0\n        ):\n            raise TypeError(\"'consecutive_failures' must be a non-negative integer\")\n        last_error = data.get(\"last_error\")\n        failure_kind = data.get(\"failure_kind\")\n        if last_error is not None and not isinstance(last_error, str):\n            raise TypeError(\"'last_error' must be a string or null\")\n        if failure_kind is not None and failure_kind not in {\"dispatch\", \"schedule\"}:\n            raise ValueError(\"'failure_kind' must be 'dispatch', 'schedule', or null\")\n        # Never raises: the store quarantines the whole file when a single\n        # record fails to load, so an unusable timezone value degrades that\n        # one job to UTC — the semantics it had before the field existed —\n        # instead of taking every other job down with it. Absent, blank, and\n        # non-string values all normalize to None.\n        raw_tz = data.get(\"timezone\")\n        tz = raw_tz if isinstance(raw_tz, str) and raw_tz.strip() else None\n        if raw_tz is not None and tz is None:\n            logger.warning(\n                \"scheduled research job %s has an unusable timezone %r; \"\n                \"evaluating its schedule in UTC\",\n                job_id,\n                raw_tz,\n            )\n        status = JobStatus(data[\"status\"])\n        title = data.get(\"title\", \"\")\n        source_type = data.get(\"source_type\", \"prompt\")\n        playbook_slug = data.get(\"playbook_slug\")","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L439-L475","documentation":"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.","triggerScenarios":"{\"failure_kind\": \"Dispatch\"} (wrong case), {\"failure_kind\": \"timeout\"}, or {\"failure_kind\": \"\"}. Only exactly 'dispatch', 'schedule', or null pass.","commonSituations":"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.","solutions":["Use exact lowercase literals 'dispatch' or 'schedule'","Map new categories to one of these two or null until the enum is extended","Normalize empty strings to null before persisting"],"exampleFix":"// before\nrecord[\"failure_kind\"] = kind.title()  # 'Dispatch'\n\n// after\nrecord[\"failure_kind\"] = kind if kind in {\"dispatch\", \"schedule\"} else None","handlingStrategy":"validation","validationCode":"VALID_KINDS = {\"dispatch\", \"schedule\"}\n\ndef failure_kind_ok(v):\n    return v is None or (isinstance(v, str) and v in VALID_KINDS)","typeGuard":"VALID_KINDS = {\"dispatch\", \"schedule\"}\n\ndef safe_failure_kind(v):\n    if isinstance(v, str):\n        v = v.lower()\n        if v in VALID_KINDS:\n            return v\n    return None","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept ValueError as exc:\n    if \"failure_kind\" in str(exc):\n        record = dict(record); record[\"failure_kind\"] = None\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["Write failure_kind only from a shared constant set","Lowercase before persisting","Use null, not empty string, for 'no classification'"],"tags":["deserialization","validation","enum","scheduled-research"],"backgroundTag":"invalid-enum-value","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}