Skip to content

scheduler

codex-scheduler stores scheduled local jobs and runs them on demand from a user-level tick command. It is dry-run by default: tick and run do not execute scripts or Codex unless --execute is provided, and Codex jobs additionally require --allow-codex.

Terminal window
npm install -g @codex-modules/scheduler

Or from source:

Terminal window
npm install
npm run build

The default store is ~/.codex-modules/scheduler/. Override it with --store-dir, API storeDir, or CODEX_SCHEDULER_HOME.

Create a Codex-backed job:

Terminal window
codex-scheduler create --schedule "every 30m" --prompt "Summarize repo health" --cwd "$PWD"

List and dry-run due work:

Terminal window
codex-scheduler list
codex-scheduler tick --now 2026-07-06T09:00:00 --json

Execute due work:

Terminal window
codex-scheduler tick --execute --allow-codex

Manual run:

Terminal window
codex-scheduler run <job-id> --execute --allow-codex

Manual runs use the same persisted claim as scheduled runs. A second manual run or tick for that job fails or skips while the first lease is active; manual runs do not consume the scheduled occurrence. Removing a job with an active claim is rejected; wait for that run to finish or for its lease to expire.

Install planning is dry-run unless --write is present:

Terminal window
codex-scheduler install-tick --interval-min 5
codex-scheduler install-tick --interval-min 5 --execute --allow-codex --write

The installer records absolute scheduler and Codex executable paths, a bounded PATH, and stdout/stderr logs under <store>/logs/. macOS uses a per-user LaunchAgent. Linux uses the user crontab and supports exact intervals that divide an hour: 1,2,3,4,5,6,10,12,15,20,30,60 minutes. launchctl and crontab failures abort the command instead of being ignored.

A loaded LaunchAgent is booted out before reinstall, then bootstrapped from the new plist. If bootstrap or enable fails, the previous plist and loaded service are restored when available. Cron command/path values containing %, CR, LF, or NUL are rejected because cron gives those bytes control semantics.

Supported schedule inputs are:

  • 30m, 2h, 1d for one-shot runs relative to creation time.
  • every 30m, every 2h for intervals.
  • ISO timestamps such as 2026-07-06T14:00:00 or 2026-07-06T14:00:00+09:00.
  • Five-field local cron: minute hour day-of-month month day-of-week.

Cron supports numbers, *, lists, ranges, and steps only. Aliases such as @daily, names such as MON, six-field cron, seconds, years, L, W, #, and ? are rejected.

The module uses the host local timezone and no timezone database dependency. Spring-forward local times can normalize to the next valid JavaScript Date. Fall-back repeated wall-clock hours are guarded by persisted nextRunAt and job claims.

Codex argv is fixed to the safe lane:

codex exec --skip-git-repo-check --ignore-user-config [-C <cwd>] -s read-only [-m <model>] [-c model_reasoning_effort=<effort>] -o <output.md> --json --ephemeral <prompt>

-a, --ask-for-approval, --dangerously-*, danger-full-access, and arbitrary -c passthrough are not generated or accepted. Child stdin is connected to the platform dev-null device through the stdio fd array.

User config.toml inheritance is off by default so an unattended run cannot silently acquire user-defined MCP servers or other config settings. Pass --inherit-user-config only for a reviewed job that needs them. Authentication still comes from the selected CODEX_HOME, as supported by Codex’s --ignore-user-config contract.

Claims are leased from the configured sequential script and Codex runtime plus a completion grace period. Each job’s lease starts at its actual claim time, immediately before that job executes. A bounded heartbeat renews long-running claims and is stopped before completion. If sleep or an event-loop stall lets a timestamp expire, another tick also verifies the same-host owner PID and OS process-start identity before clearing it. A dead PID or mismatched identity is stale; a remote or unverifiable owner is preserved to avoid duplicate execution.

script/Codex timeouts terminate the whole descendant process tree. Execution exceptions are persisted as error outputs and a tick continues with later jobs. When output retention prunes a run, its output directory is removed as well. Failed removals are recorded in pendingOutputDeletes and retried by later runs and ticks without stopping other due jobs.

Scripts are user-authored local automation, not a sandbox. Script paths must stay under the scheduler script root, cwd must be an absolute real directory outside common secret/config locations, and env is filtered before spawning.

At most 1 MB of script stdout/stderr is retained. At most 64 KiB of stdout is passed to Codex inside an explicit untrusted-data boundary; instructions in script output are not part of the job instructions.

The credential guard blocks obvious exfiltration targets such as auth.json, $CODEX_HOME, ~/.codex, common API token env names, bearer tokens, x-api-key headers, access tokens, private keys, and direct shell/network combinations involving secret files. Output redaction is best-effort and fail-closed.

Terminal window
codex-scheduler create --blueprint custom-reminder --slot message="Pay rent" --slot time=08:30 --slot recurrence=daily
codex-scheduler create --blueprint repo-health-check --slot repo="$PWD" --slot recurrence=weekdays

Available blueprints are custom-reminder and repo-health-check.

  • Cron next-run search is bounded to five years and advances minute by minute rather than using a precomputed candidate-set optimizer. This keeps the implementation dependency-free while preserving the contract’s bounded failure behavior.

Inspired by NousResearch/hermes-agent (MIT): https://github.com/NousResearch/hermes-agent