{"record":{"id":"5107243b91b35e0a","repo":"HKUDS/Vibe-Trading","slug":"end-at-must-be-an-integer-epoch-ms-or-null","errorCode":null,"errorMessage":"'end_at' must be an integer (epoch ms) or null","messagePattern":"'end_at' must be an integer \\(epoch ms\\) or null","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":484,"sourceCode":"            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\")\n        end_at = data.get(\"end_at\")\n        if not isinstance(title, str):\n            raise TypeError(\"'title' must be a string\")\n        if source_type not in {\"prompt\", \"playbook\"}:\n            raise ValueError(\"'source_type' must be 'prompt' or 'playbook'\")\n        if playbook_slug is not None and not isinstance(playbook_slug, str):\n            raise TypeError(\"'playbook_slug' must be a string or null\")\n        if end_at is not None and (isinstance(end_at, bool) or not isinstance(end_at, int)):\n            raise TypeError(\"'end_at' must be an integer (epoch ms) or null\")\n        raw_config = data.get(\"config\")\n        config: Dict[str, Any] = raw_config if isinstance(raw_config, dict) else {}\n        delivery_channel = data.get(\"delivery_channel\")\n        delivery_target = data.get(\"delivery_target\")\n        delivery_target_ref = data.get(\"delivery_target_ref\")\n        delivery_target_label = data.get(\"delivery_target_label\")\n        for name, value in (\n            (\"delivery_channel\", delivery_channel),\n            (\"delivery_target\", delivery_target),\n            (\"delivery_target_ref\", delivery_target_ref),\n            (\"delivery_target_label\", delivery_target_label),\n        ):\n            if value is not None and not isinstance(value, str):\n                raise TypeError(f\"'{name}' must be a string or null\")\n        return cls(\n            id=job_id,\n            prompt=prompt,\n            schedule=schedule,","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L466-L502","documentation":"Raised by Job from_dict (agent/src/scheduled_research/models.py:484) when the optional 'end_at' field is present, non-null, and not an int — with bools explicitly rejected (isinstance(end_at, bool) check) even though bool subclasses int. It is the job's expiry as epoch milliseconds.","triggerScenarios":"{\"end_at\": \"1767225600000\"}, {\"end_at\": 1767225600.5}, {\"end_at\": true}, or an ISO datetime string.","commonSituations":"Epoch seconds vs ms; ISO strings from default JSON encoders; a boolean 'ended' flag accidentally placed in the end_at slot.","solutions":["Write int epoch ms: int(dt.timestamp() * 1000)","Never store booleans in timestamp fields; use null for 'no end'","Convert numeric strings before persisting"],"exampleFix":"// before\nrecord[\"end_at\"] = end_date.isoformat()\n\n// after\nrecord[\"end_at\"] = int(end_date.timestamp() * 1000)","handlingStrategy":"validation","validationCode":"def end_at_ok(v):\n    return v is None or (isinstance(v, int) and not isinstance(v, bool))","typeGuard":"from typing import Any, Optional\n\ndef safe_end_at(v: Any) -> Optional[int]:\n    if v is None or isinstance(v, bool) or not isinstance(v, int):\n        return None\n    return v","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept TypeError as exc:\n    if \"end_at\" in str(exc):\n        record = dict(record); record[\"end_at\"] = None\n        job = Job.from_dict(record)\n    else:\n        raise","preventionTips":["Use the epoch-ms helper for end_at too","Store null for open-ended jobs","Keep booleans out of timestamp slots"],"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"}