{"record":{"id":"0717ab6241270026","repo":"HKUDS/Vibe-Trading","slug":"id-prompt-and-schedule-must-be-strings","errorCode":null,"errorMessage":"'id', 'prompt', and 'schedule' must be strings","messagePattern":"'id', 'prompt', and 'schedule' must be strings","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/src/scheduled_research/models.py","lineNumber":437,"sourceCode":"    def from_dict(cls, data: Dict[str, Any]) -> \"ScheduledResearchJob\":\n        \"\"\"Reconstruct a job from a plain dict.\n\n        Args:\n            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\")","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/models.py#L419-L455","documentation":"Raised by ScheduledRun/Job from_dict (agent/src/scheduled_research/models.py:437) when any of the required top-level fields 'id', 'prompt', or 'schedule' is not a string. These are the core identifying fields of a job record; note they are fetched with data[...] first, so a missing key raises KeyError instead — this error is specifically about the wrong type.","triggerScenarios":"from_dict({\"id\": 42, \"prompt\": \"...\", \"schedule\": \"@daily\"}) or schedule passed as a cron object/crontab instance rather than its string expression. Missing keys raise KeyError, not this TypeError.","commonSituations":"Using a numeric DB primary key as job id; passing a parsed cron object or croniter instance instead of the raw expression string; building dicts programmatically and leaking non-str values.","solutions":["Cast ids: str(job_id) before building the dict","Serialize schedule as its string expression (e.g. '0 9 * * 1-5' or '@daily')","Ensure all three keys are present (else KeyError) and string-typed"],"exampleFix":"// before\nrecord = {\"id\": job.pk, \"prompt\": prompt, \"schedule\": cron_obj}\n\n// after\nrecord = {\"id\": str(job.pk), \"prompt\": prompt, \"schedule\": \"0 9 * * 1-5\"}","handlingStrategy":"validation","validationCode":"def core_strings_ok(d):\n    return all(isinstance(d.get(k), str) and k in d for k in (\"id\", \"prompt\", \"schedule\"))","typeGuard":"from typing import Any, Dict, Optional\n\ndef normalized_core(data: Dict[str, Any]) -> Optional[Dict[str, Any]]:\n    out = dict(data)\n    for k in (\"id\", \"prompt\", \"schedule\"):\n        if k in out and out[k] is not None:\n            out[k] = str(out[k])\n    missing = [k for k in (\"id\", \"prompt\", \"schedule\") if k not in out]\n    return None if missing else out","tryCatchPattern":"try:\n    job = Job.from_dict(record)\nexcept (TypeError, KeyError) as exc:\n    log.warning(\"dropping malformed job record: %r (%s)\", record, exc)\n    job = None","preventionTips":["Always build records via constructors/to_dict, not literal dicts","Stringify DB ids at the boundary","Pass cron expressions as strings, never parsed objects"],"tags":["deserialization","type-error","cron","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"}