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_id

View on GitHub (pinned to 985456f4ad)

Solutions

  1. 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().
  2. When scripting job creation, always obtain IDs from new_cron_id() instead of inventing them.
  3. 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

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


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/b0c1fb7caadd3b83. Report an issue: GitHub.