#17529: feat(cron): add preCheck gate to skip jobs when nothing changed
docs
stale
size: M
Cluster:
Cron Job Stability Fixes
## Problem
Recurring cron jobs wake an agent every N minutes even when there's nothing to act on. This wastes tokens on "nothing new" / `HEARTBEAT_OK` responses. Users currently have no way to gate a cron job on a lightweight condition check — every scheduled tick triggers a full agent turn.
**Real-world example:** A cron job that reviews open PRs every 30 minutes. If there are no open PRs, it still spends tokens asking the agent to check and getting "no PRs to review" back.
## Solution
Add an optional `preCheck` field to cron jobs: a lightweight shell command that runs **before** the agent turn. If the command fails or returns empty output, the job is skipped entirely — no tokens spent.
```bash
openclaw cron add \
--name "PR review" \
--every "30m" \
--session isolated \
--message "Review these open PRs and flag any issues." \
--pre-check 'gh pr list --state open --json number | jq "if length > 0 then . else empty end"'
```
### How it works
| Pre-check result | Job outcome |
|---|---|
| Exit 0 + non-empty stdout | ✅ Job proceeds — stdout optionally prepended as context |
| Exit non-zero | ⏭️ Job skipped (status: `skipped`) |
| Empty stdout | ⏭️ Job skipped (nothing to do) |
| Timeout | ⏭️ Job skipped |
### preCheck fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `command` | string | *required* | Shell command to execute |
| `timeoutSeconds` | number | 30 | Kill + skip on timeout |
| `outputMode` | `"prepend"` \| `"replace"` \| `"ignore"` | `"prepend"` | How to use stdout in the agent message |
### Output modes
- **prepend** (default): Prepend stdout to the agent message as `[Pre-check context]`
- **replace**: Use stdout as the entire agent message
- **ignore**: Discard stdout, just use the pass/fail gate
### Use cases
- Only review PRs when there are open PRs (`gh pr list`)
- Only summarize emails when new mail arrived
- Only report on deployments when something deployed
- Only analyze logs when error count exceeds threshold (`grep -c ERROR log | awk '$1>0'`)
- Check a file's mtime before processing it
- Query an API endpoint for changes before waking the agent
## Implementation
- **`src/cron/types.ts`** — `CronPreCheck` type, added to `CronJob` and `CronJobPatch`
- **`src/cron/pre-check.ts`** — `runPreCheck()` executor and `applyPreCheckOutput()` helper
- **`src/cron/service/timer.ts`** — Integrated gate into `executeJobCore()`
- **`src/cron/service/jobs.ts`** — Wired preCheck into create and patch logic
- **`src/cron/normalize.ts`** — preCheck field normalization
- **`docs/automation/cron-jobs.md`** — Added 'Pre-check gate' section with examples
## Testing
- [x] 11 unit tests for pre-check module
- [x] 6 integration tests for patch lifecycle
- [x] Build and Lint pass
- [x] Cross-platform (Linux/Windows) verified in CI
- [x] Backward compatible — jobs without `preCheck` are unaffected
## Notes
- AI-assisted PR (Claude) — fully tested, code reviewed and understood
- This is a feature proposal — happy to adjust the API surface based on maintainer feedback
- CLI flags (`--pre-check`, `--pre-check-timeout`, `--pre-check-output`) not included in this PR to keep scope focused; can add in a follow-up
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds an optional `preCheck` gate to cron jobs — a lightweight shell command that runs before the agent turn and skips the job (saving tokens) when the command fails or returns empty output. The implementation cleanly threads through the existing cron architecture: types, normalization, create/patch, and both `main` and `isolated` execution paths. Tests cover core pre-check behavior and patch lifecycle.
- `src/cron/pre-check.ts` — The `runPreCheck` function is missing a `settled` guard flag that exists in the codebase's established pattern (`src/process/exec.ts:146-179`). When `maxBuffer` is exceeded, Node.js `exec()` emits both `error` and `close` events, causing `resolve()` to be called twice.
- `src/cron/service/timer.ts` — Uses `await import("node:path")` dynamically inside `executeJobCore` on every job tick; should be a static top-level import.
- Types, normalization, job service changes, tests, and docs are all well-structured and consistent with existing patterns.
<h3>Confidence Score: 3/5</h3>
- Mostly safe to merge after addressing the missing `settled` guard in the pre-check executor to prevent double-resolution edge cases.
- The feature is well-designed and backward compatible, with good test coverage and clean integration. The missing `settled` guard in `runPreCheck` is a real (though low-probability) correctness issue when `maxBuffer` is exceeded, and should be fixed before merge to match the codebase's established pattern. The dynamic import is a minor style issue.
- `src/cron/pre-check.ts` needs a `settled` guard to prevent double-resolution when `error` and `close` both fire.
<sub>Last reviewed commit: 063e2be</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#20492: feat(cron): gate script evaluated before agent turn
by clawmander · 2026-02-19
81.4%
#17064: fix(cron): prevent control-plane starvation during startup catch-up...
by donggyu9208 · 2026-02-15
78.3%
#17561: fix(cron): add runtime staleness guard for runningAtMs (#17554)
by robbyczgw-cla · 2026-02-15
76.6%
#20521: feat(heartbeat): inject active cron job summary into heartbeat prompt
by maximalmargin · 2026-02-19
76.0%
#18144: fix(cron): clear stuck runningAtMs after timeout and add maintenanc...
by taw0002 · 2026-02-16
75.7%
#17895: fix(cron): add staleness check for runningAtMs on manual trigger
by PlayerGhost · 2026-02-16
75.3%
#13065: fix(cron): Fix "every" schedule not re-arming after gateway restart
by trevorgordon981 · 2026-02-10
75.3%
#22411: fix(cron): cancel timed-out runs before side effects
by Takhoffman · 2026-02-21
75.1%
#11657: fix(cron): treat skipped heartbeat as ok for one-shot jobs
by DukeDeSouth · 2026-02-08
75.0%
#17664: fix(cron): detect and clear stale runningAtMs marker in manual run ...
by echoVic · 2026-02-16
74.8%