shareAI-lab/learn-claude-code · warning · ValueError
invalid job ID
Error message
invalid job ID
What it means
Raised during load_durable_jobs() in s12_cron_scheduler/code.py:392 when a saved job's id does not start with the literal prefix 'cron_'. Job IDs are minted by new_cron_id() as 'cron_' + 8 hex chars, and the prefix is a cheap validity invariant enforced on reload; this per-job ValueError is caught and logged ('skipped invalid saved job'), and loading continues with the remaining jobs — one bad ID does not abort the whole scheduler load.
Source
Thrown at s12_cron_scheduler/code.py:392
return
try:
payload = json.loads(DURABLE_PATH.read_text())
if not isinstance(payload, list):
raise ValueError("expected a JSON list")
except (OSError, json.JSONDecodeError, ValueError) as error:
print(f" [cron] could not load {DURABLE_PATH.name}: {error}")
return
loaded = 0
with cron_lock:
for item in payload:
try:
job = CronJob(**item)
error = validate_cron(job.cron)
if error:
raise ValueError(error)
if not job.id.startswith("cron_"):
raise ValueError("invalid job ID")
if not job.prompt.strip():
raise ValueError("prompt cannot be empty")
except (TypeError, ValueError) as error:
print(f" [cron] skipped invalid saved job: {error}")
continue
scheduled_jobs[job.id] = job
if job.pending_delivery:
cron_queue.append(job)
loaded += 1
if loaded:
print(f" [cron] loaded {loaded} durable job(s)")
def new_cron_id() -> str:
for _ in range(100):
job_id = f"cron_{secrets.token_hex(4)}"
if job_id not in scheduled_jobs:
return job_idView on GitHub (pinned to 985456f4ad)
Solutions
- Restore the 'cron_' prefix on the id (and keep the rest of the 8-hex-char convention, e.g. 'cron_ab12cd34'), or regenerate the job via schedule_job().
- When scripting job creation, always obtain IDs from new_cron_id() instead of inventing them.
- After fixing, reload and confirm the '[cron] loaded N durable job(s)' count matches expectations.
Example fix
# before: .scheduled_tasks.json entry
{"id": "nightly-build", "cron": "0 3 * * *", "prompt": "run build"}
# after
{"id": "cron_1a2b3c4d", "cron": "0 3 * * *", "prompt": "run build", "recurring": true, "durable": true} Defensive patterns
Strategy: validation
Validate before calling
def job_id_is_valid(job_id) -> bool:
return isinstance(job_id, str) and job_id.startswith('cron_') Type guard
def is_cron_job_id(value) -> bool:
return isinstance(value, str) and value.startswith('cron_') Try / catch
# loader already skips bad jobs; catch at the scheduling boundary
for job in payload:
if not is_cron_job_id(job.get('id')):
log.warning('dropping job with foreign id %r', job.get('id'))
continue
register(job) Prevention
- Always mint IDs with new_cron_id(); never invent your own scheme.
- After editing the durable file, count the 'loaded N durable job(s)' line against your expectations.
- Lint the file for id prefixes before restarting the scheduler.
When it happens
Trigger: A .scheduled_tasks.json entry whose id is 'job_1', an empty string, or a bare UUID; hand-edited files where the id field was renamed or dropped (a missing id raises TypeError, also skipped); IDs generated by an external script that uses its own naming scheme.
Common situations: Manual editing of the durable file; migrating from another scheduler whose IDs lack the cron_ prefix; version drift where an older build used different IDs.
Related errors
- prompt cannot be empty
- Task {task_id} not found
- Task {task_id} not found
- Invalid task ID: {task_id!r}
- expected a JSON list
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/b0c1fb7caadd3b83.
Report an issue: GitHub.