{"record":{"id":"05f897431212d5c9","repo":"HKUDS/Vibe-Trading","slug":"next-run-at-and-created-at-must-be-integers-e","errorCode":null,"errorMessage":"'next_run_at' and 'created_at' must be integers (epoch ms)","messagePattern":"'next_run_at' and 'created_at' must be integers \\(epoch ms\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":441,"sourceCode":"            data: A raw dict as produced by :meth:`to_dict`.\n\n        Returns:\n            The reconstructed ``ScheduledResearchJob``.\n\n        Raises:\n            KeyError: If a required field is missing.\n            TypeError: If a field has the wrong type.\n            ValueError: If ``status`` is not a recognized ``JobStatus`` value.\n        \"\"\"\n        job_id = data[\"id\"]\n        prompt = data[\"prompt\"]\n        schedule = data[\"schedule\"]\n        if not isinstance(job_id, str) or not isinstance(prompt, str) or not isinstance(schedule, str):\n            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","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L423-L459","documentation":"Raised by Job from_dict (agent/src/scheduled_research/models.py:441) when the required 'next_run_at' or 'created_at' fields are not integers. Both are epoch-millisecond timestamps and must be int; floats, numeric strings, or ISO date strings fail. Keys are fetched with data[...], so absence raises KeyError first.","triggerScenarios":"{\"created_at\": \"1767225600000\"}, {\"next_run_at\": 1767225600.0}, or writing datetime.isoformat() into either field.","commonSituations":"Epoch seconds vs milliseconds confusion; string-encoded numbers from JSON produced by another language or query params; ISO strings from a default datetime JSON encoder.","solutions":["Write epoch ms ints: int(dt.timestamp() * 1000)","Convert numeric strings: int(value)","Standardize on milliseconds everywhere in your persistence layer"],"exampleFix":"// before\n{\"created_at\": \"2026-01-01T00:00:00Z\", \"next_run_at\": \"1767225600000\"}\n\n// after\n{\"created_at\": 1767225600000, \"next_run_at\": 1767312000000}","handlingStrategy":"validation","validationCode":"def epoch_int_ok(d):\n    return all(isinstance(d.get(k), int) and not isinstance(d.get(k), bool)\n               for k in (\"next_run_at\", \"created_at\"))","typeGuard":"from typing import Any, Dict\n\ndef with_epoch_ints(data: Dict[str, Any]) -> Dict[str, Any]:\n    out = dict(data)\n    for k in (\"next_run_at\", \"created_at\"):\n        v = out.get(k)\n        if isinstance(v, bool):\n            raise TypeError(f\"{k} cannot be bool\")\n        if isinstance(v, str):\n            out[k] = int(v)\n        elif isinstance(v, float):\n            out[k] = int(v)\n    return out","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept TypeError as exc:\n    if \"epoch ms\" in str(exc):\n        record = {**record, \"created_at\": int(record[\"created_at\"]),\n                  \"next_run_at\": int(record[\"next_run_at\"])}\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["Single epoch-ms helper for all timestamps","Reject string timestamps at ingestion","Use ints, not floats, in all persisted timestamp math"],"tags":["deserialization","timestamp","epoch-milliseconds","scheduled-research"],"backgroundTag":"timestamp-format-mismatch","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}