Skip to main content
Glama
README.md
# croncron

**The easiest way to run cron jobs.** Plain-English schedules, automatic logs & run history, missed-run detection, and an MCP server so AI agents can manage your jobs. Zero dependencies — one folder of TypeScript that Node ≥23.6 runs directly.

```
$ croncron add "every 5 minutes" ./backup.sh
✓ backup — every 5 minutes (*/5 * * * *)
  runs: ./backup.sh  in /Users/you/project
  next run in 3m (8/3/2026, 4:05:00 PM)
  logs: croncron logs backup · test now: croncron run backup
```

No cron syntax to remember, no `>> /tmp/foo.log 2>&1` boilerplate, no wondering whether it ran.

## Why

Raw crontab has six chronic problems — croncron fixes each one:

| crontab pain | croncron |
|---|---|
| unreadable syntax | `"weekdays at 9:30"`, `"every monday at 8pm"` — cron syntax still accepted |
| no logs without redirect boilerplate | every run's output auto-captured → `croncron logs <name>` |
| no run history / silent failures | `croncron runs <name>`: when, duration, exit code; failures flagged in `list` |
| crontab = unowned junk drawer | named jobs in a registry; `croncron import` adopts your existing lines |
| cron's minimal PATH breaks commands | PATH + shell + cwd captured at add time, replayed at run time |
| macOS sleep silently skips runs | `list` shows a ⚠ + the watchdog re-runs missed jobs after wake |
| failures go unnoticed for weeks | native notification (+ optional Telegram) on failure and on recovery |

Your crontab stays yours: croncron manages one clearly marked block and never touches anything outside it. Every real crontab write is backed up to `~/.croncron/backups/` first.

## Install

```sh
npm install -g git+https://github.com/itsnotrwanman/croncron.git
croncron doctor   # sanity-check the wiring
```

Requires Node ≥ 23.6. Website: [croncron.vercel.app](https://croncron.vercel.app)

From a clone: `pnpm link --global`.

## Commands

```
croncron add "<schedule>" <command...>   # --name --cwd --retry N --ping URL --no-notify
croncron edit <name> [--schedule ...]    # change anything without rm/re-add
croncron list                            # schedule · next run · last result · missed-run warnings
croncron run <name>                      # run right now, streamed
croncron logs <name> [lines]
croncron runs <name>                     # run history
croncron pause <name> / resume <name>
croncron rm <name>
croncron import [--apply]                # adopt existing crontab entries (dry-run by default)
croncron watchdog install|remove|status  # 24/7 mode (see below)
croncron catchup                         # run missed jobs now
croncron notify test · croncron config   # failure alerts (macOS built-in, Telegram optional)
croncron doctor                          # env/wiring checks + macOS caveats
croncron mcp                             # MCP server on stdio
```

## Reliability

- **Alerts on state change only** — one notification when a job starts failing, one when it
  recovers. A failing every-2-minutes job never becomes an alert storm. macOS notifications
  work out of the box; add Telegram in `~/.croncron/config.json`.
- **Retries** — `--retry 2` re-attempts a failed run (10s apart) before it counts as failed.
- **No overlap** — if a run is still going when the next fire comes, the new one is skipped
  and recorded as skipped (visible in `croncron runs`).
- **Monitor pings** — `--ping https://hc-ping.com/UUID` hits a healthchecks-style URL after
  every run (`/fail` on failure), for off-machine monitoring.
- **Bounded disk** — logs rotate past ~1 MB, history is trimmed; nothing grows forever.
- **Watchdog** — launchd (macOS) or systemd user timer (Linux) re-runs schedules missed
  while the machine slept.

Schedules: `every 5 minutes` · `hourly` · `daily at 9am` · `weekdays at 9:30` · `every monday at 8pm` · `weekends at 10am` · `monthly on the 1st at 9am` · any raw cron expression.

## For AI agents

### Claude Code
```sh
claude mcp add --scope user croncron -- croncron mcp
```
Then just ask: *"schedule my backup script every night at 3am"*. Tools exposed: `cron_add`, `cron_list`, `cron_remove`, `cron_pause`, `cron_run_now`, `cron_logs`.

### Codex CLI (`~/.codex/config.toml`)
```toml
[mcp_servers.croncron]
command = "croncron"
args = ["mcp"]
```

### Kimi CLI / any MCP client
```json
{ "mcpServers": { "croncron": { "command": "croncron", "args": ["mcp"] } } }
```

### Shell-only agents (no MCP)
Every read/write command takes `--json` for machine-readable output:
```sh
croncron add "every 10 minutes" "curl -s https://api.example.com/ping" --json
croncron list --json     # includes nextRun, lastRun, missedLastScheduledRun
croncron run backup --json
```

## 24/7 mode (the macOS sleep trap)

cron only fires while the machine is awake — a job scheduled during sleep is skipped
forever, silently. `croncron watchdog install` fixes this with a launchd LaunchAgent
(launchd *does* run after wake) that calls `croncron catchup` every 5 minutes:

- any enabled job whose last scheduled fire has no run record gets executed once
- jobs cron will fire again within 10 minutes are skipped (no duplicate runs)
- catch-up runs are marked `catchup: true` in history and in the job log

So a `daily at 3am` backup on a laptop that was closed at 3am runs minutes after you
open the lid, instead of never.

## How it works

- `~/.croncron/jobs.json` — the registry (name, schedule, command, cwd, shell, PATH)
- `~/.croncron/logs/<name>.log` — every run's output, timestamped
- `~/.croncron/history/<name>.jsonl` — one line per run: start, duration, exit code
- crontab gets one managed block; each entry calls `croncron exec <name>` with absolute paths, so cron's minimal environment doesn't matter

## Development

```sh
node --test "test/*.test.ts"   # 57 tests: parser, crontab block, importer, CLI e2e, MCP over stdio
```

Tests run against a sandboxed `CRONCRON_HOME` and a fake crontab file (`CRONCRON_CRONTAB_FILE`) — they never touch your real crontab.