Introduction¶
The problem with scheduled tasks is often not that they fail to run, but that they stop running and no one knows: the entire crontab line is commented out, launchd log paths are invalid, or the task’s exit code has been 1 for a long time. These issues are only discovered days later when you notice the data hasn’t been updated. DSH’s philosophy is “everything is a plugin,” so local health checks can also be implemented as a plugin. Below is an introduction to feely0208/dsh-cron-guard (published name @deepwhale-cn/dsh-cron-guard), which turns the question of “whether these local scheduled tasks are still running” into a health check.
What is it¶
A DSH plugin maintained by feely0208, positioned as a read-only scheduled task guardian: it scans local launchd and cron tasks to determine which have silently stopped and which have left no trace of running. It does not modify crontab, does not write plist, does not restart tasks, does not connect to the network, and does not upload data. The author explicitly states in the README that if not installed, you will have to wait for data not to update to discover that the entire crontab is commented out, log paths are invalid, or the exit code remains 1.
Scope¶
Scans the following locations in one go:
~/Library/LaunchAgents/Library/LaunchAgents- User crontab
- On Linux, additionally tries to read
/etc/crontaband/etc/cron.d/*
How to Determine “If It’s Running”¶
This is the core design of this plugin. The conclusion in the README is: launchd’s runs (cumulative run count) and last exit code are the criteria for “running or not”; the log’s mtime only proves “whether there is evidence”.
It is divided into two layers:
- For launchd, read
runs/last exit code/job statevialaunchctl print gui/<uid>/<label>; ifrunsdoes not increase during two consecutive scans and no success has occurred over N scheduled cycles, it is considered stopped. - If a script is designed to “exit 0 silently after today’s dispatch is complete,” the logs will not be updated for a long time and will not be misjudged as stopped.
- Tasks that do not write logs can be configured with a heartbeat file; each time triggered, it overwrites a line with a timestamp; a fresh heartbeat counts as success.
Cron has no runs to view, so it relies on log analysis.
What Evidence is Output¶
Each task provides: last run, last success, time since last success, last exit code, consecutive failure count, and log tail. The log tail will automatically redact tokens, emails, and passwords.
The plugin also identifies several real pitfalls:
- Tasks starting with
#OLD-that have been commented out - Plists missing
RunAtLoad/StartInterval/StartCalendarInterval/WatchPaths, causing them to never run automatically - Log files that do not exist or are 0 bytes (meaning no evidence was left)
Disabled=true- Empty plist placeholders
Four Tiers of Conclusion and Exit Codes¶
The judgment results are divided into four tiers, with text, --json, and --md output options:
| Tier | Situation |
|---|---|
| Red (Stopped) | launchd runs not increasing and no success over N cycles; cron judged by logs; #OLD- commented tasks; no trigger conditions at all |
| Yellow (No Evidence / Has Failure) | Log file does not exist or is 0 bytes; last exit code non-zero; not loaded, Disabled=true, empty plist; one-time task date has passed but never ran |
| Gray (One-time) | One-time tasks with StartCalendarInterval containing Year / specific dates where the date has passed and the task has already run; informational only, no action needed |
| Pass | All others; the output will include “Criteria for determining if it’s running” (runs / exit code) |
Exit codes: 0 for Normal, 1 for Red, 2 for Yellow only. Can be directly integrated into CI or inspection scripts.
Installation and Enablement¶
Install in DSH:
dsh plugin --profile web add @deepwhale-cn/dsh-cron-guard
After installation, a “Scheduled Task Guardian” panel will appear in the sidebar, listing red/yellow tasks and “how long since success,” with an option to switch thresholds (2/3/5 cycles).
The CLI can also be run separately without installing it into the host, requiring Node ≥ 18:
node bin/cli.mjs # Scan real local tasks
node bin/cli.mjs --json # For scripts
node bin/cli.mjs --periods 3 --list
node bin/cli.mjs --help
Configuration Options¶
Configuration file .cron-guard.json is searched in order: cwd → ~, or you can specify it with --config:
{
"periods": 2,
"heartbeats": {
"com.example.job": "~/path/job.last"
},
"successRe": "成功|RUN_OK",
"failRe": "失败|error"
}
Command-line equivalents: --heartbeat com.example.job=~/path/job.last (repeatable), --periods 3.
The runs snapshot is written by default to ~/Library/Application Support/dsh-cron-guard/state.json, storing only label + runs + time, containing no business data; it can be disabled with --no-state or the path changed with DSH_CRON_GUARD_STATE.
Testing and Security¶
Run tests:
bash tests/run.sh
Tests only create fixtures in an mktemp directory (fake HOME + fake crontab + fake launchctl output), and do not read real crontab or access the network.
Suitable Scenarios and Notes¶
Suitable for Development and Operations scenarios running local launchd / cron scheduled tasks and wishing to detect “silent stoppage” early. The plugin’s scanning and judgment rely on the permissions of the current dsh process; how many tasks can be seen depends on the running identity. Before installing, you should check the source code and license (the repository is marked as MIT) before deciding whether to use it.
Conclusion¶
What dsh-cron-guard adds is not “how to configure tasks,” but “whether configured tasks are still running.” See the directory page and source code at:
- Directory page: https://www.skillhub.cn/plugins/feely0208/dsh-cron-guard
- GitHub: https://github.com/feely0208/dsh-cron-guard