shareAI-lab/learn-claude-code · warning · ValueError

prompt cannot be empty

Error message

prompt cannot be empty

What it means

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.

Source

Thrown at s12_cron_scheduler/code.py:394

        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
    raise RuntimeError("Could not allocate a cron job ID")

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Give the job a non-empty prompt (any non-blank string) or delete the entry entirely rather than keeping a hollow job.
  2. Create prompts only through schedule_job(), which rejects blank prompts at creation time ('Prompt cannot be empty').
  3. After editing, rerun and check the '[cron] loaded N durable job(s)' line to confirm all intended jobs survived.

Example fix

# before
{"id": "cron_1a2b3c4d", "cron": "0 3 * * *", "prompt": ""}

# after
{"id": "cron_1a2b3c4d", "cron": "0 3 * * *", "prompt": "Summarize yesterday's commits"}
Defensive patterns

Strategy: validation

Validate before calling

def job_is_loadable(job: dict) -> bool:
    return isinstance(job.get('prompt'), str) and bool(job['prompt'].strip())

Type guard

def has_prompt(job) -> bool:
    return isinstance(job, dict) and isinstance(job.get('prompt'), str) and bool(job['prompt'].strip())

Try / catch

for job in payload:
    if not has_prompt(job):
        log.warning('dropping promptless job %s', job.get('id'))
        continue
    register(job)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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