{"record":{"id":"1814a81db592dcd0","repo":"HKUDS/Vibe-Trading","slug":"last-error-must-be-a-string-or-null","errorCode":null,"errorMessage":"'last_error' must be a string or null","messagePattern":"'last_error' must be a string or null","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":455,"sourceCode":"            raise TypeError(\"'id', 'prompt', and 'schedule' must be strings\")\n        next_run_at = data[\"next_run_at\"]\n        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\", \"\")","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L437-L473","documentation":"Raised by Job from_dict (agent/src/scheduled_research/models.py:455) when the optional 'last_error' field is present but is not a string and not null. It holds the message of the most recent failure; dicts, lists, or exception objects serialized as structures fail this check.","triggerScenarios":"{\"last_error\": {\"type\": \"TimeoutError\", \"msg\": \"...\"}} or storing repr output of an exception object that isn't a plain string; absent/null is fine.","commonSituations":"Structured error logging leaking into the field; passing an exception instance where str(exc) was intended.","solutions":["Store str(exc) or exc.__repr__() as a plain string","Flatten structured error info into one message line","Omit/null the field when there is no error"],"exampleFix":"// before\nrecord[\"last_error\"] = {\"msg\": str(exc)}\n\n// after\nrecord[\"last_error\"] = str(exc)","handlingStrategy":"validation","validationCode":"def last_error_ok(d):\n    v = d.get(\"last_error\")\n    return v is None or isinstance(v, str)","typeGuard":"def safe_last_error(v):\n    return v if isinstance(v, str) else (None if v is None else str(v))","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept TypeError as exc:\n    if \"last_error\" in str(exc):\n        record = dict(record); record[\"last_error\"] = None\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["Store str(exc), never the exception or a dict","Null the field on successful runs","Format structured errors into one line before persisting"],"tags":["deserialization","type-error","error-message","scheduled-research"],"backgroundTag":"json-field-type-mismatch","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}