{"record":{"id":"964705d10fb93f29","repo":"shareAI-lab/learn-claude-code","slug":"prompt-cannot-be-empty","errorCode":null,"errorMessage":"prompt cannot be empty","messagePattern":"prompt cannot be empty","errorType":"console","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"s12_cron_scheduler/code.py","lineNumber":394,"sourceCode":"        payload = json.loads(DURABLE_PATH.read_text())\n        if not isinstance(payload, list):\n            raise ValueError(\"expected a JSON list\")\n    except (OSError, json.JSONDecodeError, ValueError) as error:\n        print(f\"  [cron] could not load {DURABLE_PATH.name}: {error}\")\n        return\n\n    loaded = 0\n    with cron_lock:\n        for item in payload:\n            try:\n                job = CronJob(**item)\n                error = validate_cron(job.cron)\n                if error:\n                    raise ValueError(error)\n                if not job.id.startswith(\"cron_\"):\n                    raise ValueError(\"invalid job ID\")\n                if not job.prompt.strip():\n                    raise ValueError(\"prompt cannot be empty\")\n            except (TypeError, ValueError) as error:\n                print(f\"  [cron] skipped invalid saved job: {error}\")\n                continue\n            scheduled_jobs[job.id] = job\n            if job.pending_delivery:\n                cron_queue.append(job)\n            loaded += 1\n    if loaded:\n        print(f\"  [cron] loaded {loaded} durable job(s)\")\n\n\ndef new_cron_id() -> str:\n    for _ in range(100):\n        job_id = f\"cron_{secrets.token_hex(4)}\"\n        if job_id not in scheduled_jobs:\n            return job_id\n    raise RuntimeError(\"Could not allocate a cron job ID\")\n","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s12_cron_scheduler/code.py#L376-L412","documentation":"Raised during load_durable_jobs() in s12_cron_scheduler/code.py:394 when a saved job's prompt field is empty after stripping. Every cron job exists to deliver a prompt, so a blank prompt makes the job a no-op; on reload each job is validated in sequence (cron expression, id prefix, then prompt) and this failure is caught per-job, logged as 'skipped invalid saved job', and the rest of the file loads normally.","triggerScenarios":"A .scheduled_tasks.json entry with \"prompt\": \"\" or \"   \"; a prompt key missing entirely (TypeError, also skipped); hand-edited entries where the prompt text was deleted; a foreign writer that persisted the prompt under a different key name.","commonSituations":"Manual pruning of the durable file that empties prompts; migration scripts that drop the field; testing edits that replace the prompt with a placeholder whitespace string.","solutions":["Give the job a non-empty prompt (any non-blank string) or delete the entry entirely rather than keeping a hollow job.","Create prompts only through schedule_job(), which rejects blank prompts at creation time ('Prompt cannot be empty').","After editing, rerun and check the '[cron] loaded N durable job(s)' line to confirm all intended jobs survived."],"exampleFix":"# before\n{\"id\": \"cron_1a2b3c4d\", \"cron\": \"0 3 * * *\", \"prompt\": \"\"}\n\n# after\n{\"id\": \"cron_1a2b3c4d\", \"cron\": \"0 3 * * *\", \"prompt\": \"Summarize yesterday's commits\"}","handlingStrategy":"validation","validationCode":"def job_is_loadable(job: dict) -> bool:\n    return isinstance(job.get('prompt'), str) and bool(job['prompt'].strip())","typeGuard":"def has_prompt(job) -> bool:\n    return isinstance(job, dict) and isinstance(job.get('prompt'), str) and bool(job['prompt'].strip())","tryCatchPattern":"for job in payload:\n    if not has_prompt(job):\n        log.warning('dropping promptless job %s', job.get('id'))\n        continue\n    register(job)","preventionTips":["Create jobs only through schedule_job(), which rejects blank prompts at creation.","Delete unwanted jobs entirely instead of blanking their prompt.","Check the loaded-jobs count after every manual file edit."],"tags":["cron","validation","persistence"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}