shareAI-lab/learn-claude-code · error · RuntimeError

Could not allocate a cron job ID

Error message

Could not allocate a cron job ID

What it means

Raised by new_cron_id() in s12_cron_scheduler/code.py:411 after 100 attempts to mint an unused 'cron_' + 8-hex-char ID, every one of which was already present in the in-memory scheduled_jobs dict. IDs come from secrets.token_hex(4) (32 bits); with a normal number of jobs, 100 random draws colliding is astronomically improbable, so hitting this error in practice signals a degenerate state — e.g. scheduled_jobs pre-populated with an enormous or exhaustive ID set, or a bug seeding the dict with every generated candidate.

Source

Thrown at s12_cron_scheduler/code.py:411

                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")


def schedule_job(cron: str, prompt: str, recurring: bool = True,
                 durable: bool = True) -> CronJob | str:
    error = validate_cron(cron)
    if error:
        return error
    if not prompt.strip():
        return "Prompt cannot be empty"

    with cron_lock:
        job = CronJob(
            id=new_cron_id(),
            cron=cron,
            prompt=prompt,
            recurring=recurring,
            durable=durable,
        )

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Inspect len(scheduled_jobs) and the contents of .scheduled_tasks.json — if the dict is unexpectedly full of IDs, the state is corrupted; clear or prune it.
  2. Widen the ID space (e.g. secrets.token_hex(8) with the matching prefix check) if you legitimately need billions of jobs, and migrate old IDs.
  3. In code that manages scheduled_jobs manually, never insert a candidate ID before new_cron_id() returns it.

Example fix

# before
job_id = f'cron_{secrets.token_hex(4)}'

# after (wider space; also relax the reload prefix check to accept 16 hex chars)
job_id = f'cron_{secrets.token_hex(8)}'
Defensive patterns

Strategy: retry

Validate before calling

def id_space_is_healthy(scheduled_jobs: dict) -> bool:
    return len(scheduled_jobs) < 1_000_000  # far below any realistic collision risk

Try / catch

try:
    job_id = new_cron_id()
except RuntimeError:
    # state is degenerate; clear or prune scheduled_jobs, then retry once
    prune_or_reset_scheduled_jobs()
    job_id = new_cron_id()

Prevention

When it happens

Trigger: A seeded/mocked scheduled_jobs containing huge numbers of IDs (approaching 2^32) before calling new_cron_id(); a loop bug that inserts each candidate ID before checking, guaranteeing collisions; corrupted state loaded from a tampered .scheduled_tasks.json with an ID flood. Real-world stores with dozens of jobs cannot trigger this.

Common situations: Tests that exhaustively seed the ID space to verify the guard; tampered or machine-generated durable files containing thousands of IDs (still far from the threshold); bugs in code that snapshots and restores scheduled_jobs.

Related errors


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