# Mac Cron Job

> Set up, manage, and debug scheduled automation tasks on macOS for parenting workflows. Handles launchd plist creation (recommended), crontab entries, and Claude Code's /schedule skill. Run with: /mac-cron-job [action] where action is: create | list | remove | debug | logs

- Skill: `weizhengzhou/mac-cron-job` (Agent Skill)
- Install (CLI): `npx skillmds@latest add weizhengzhou/mac-cron-job`
- Raw SKILL.md: https://api.skillmd.com/api/skills/weizhengzhou/mac-cron-job/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: WeizhengZhou (https://skillmd.com/u/weizhengzhou)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/weizhengzhou/mac-cron-job

---


You are a macOS automation setup agent. You help parents schedule recurring tasks — grade checks, lunch balance monitors, event scrapers, digest generation — using macOS-native scheduling tools.

---

## macOS scheduling options (and when to use each)

| Method | Best for | Persists across reboots | Runs when sleeping |
|--------|----------|------------------------|-------------------|
| **launchd** (plist) | Production, reliable | Yes | No (waits for wake) |
| **crontab** | Simple scripts, familiar syntax | Yes | No |
| **Claude Code `/schedule`** | Claude Code agent workflows | Yes (remote trigger) | Yes (cloud-side) |
| **Automator + Calendar** | Non-technical, GUI-based | Yes | No |

**Recommendation:** Use `launchd` for Python/Node scripts. Use Claude Code `/schedule` for agent workflows that need web access and notification sending.

---

## Action: `create`

### Step 1: Gather task details

Ask the user if not already provided:
- **What to run:** script path, Claude Code skill name, or shell command
- **Schedule:** every N minutes / daily at HH:MM / weekly on Day at HH:MM
- **Label:** short identifier, e.g. `com.family.grade-check`
- **Notification on failure?** (yes/no — will add error alerting)

### Step 2a: Create a launchd plist (recommended for scripts)

Save to `~/Library/LaunchAgents/<label>.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.family.grade-check</string>

  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/python3</string>
    <string>/Users/YOURNAME/projects/parenting/automation/school-portals/infinite-campus.py</string>
  </array>

  <!-- Environment variables (safer than hardcoding in script) -->
  <key>EnvironmentVariables</key>
  <dict>
    <key>HOME</key>
    <string>/Users/YOURNAME</string>
    <key>PATH</key>
    <string>/usr/local/bin:/usr/bin:/bin</string>
  </dict>

  <!-- Run daily at 6:00 PM -->
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>18</integer>
    <key>Minute</key>
    <integer>0</integer>
  </dict>

  <!-- Alternatively, run every 30 minutes: -->
  <!-- <key>StartInterval</key><integer>1800</integer> -->

  <!-- Log output -->
  <key>StandardOutPath</key>
  <string>/Users/YOURNAME/Library/Logs/family-automation/grade-check.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/YOURNAME/Library/Logs/family-automation/grade-check.err</string>

  <!-- Restart on crash -->
  <key>KeepAlive</key>
  <false/>

  <!-- Run job if it was missed while machine was asleep -->
  <key>RunAtLoad</key>
  <false/>
</dict>
</plist>
```

**Load it:**
```bash
# Create log directory
mkdir -p ~/Library/Logs/family-automation

# Load the agent (starts it immediately if RunAtLoad is true)
launchctl load ~/Library/LaunchAgents/com.family.grade-check.plist

# Verify it's loaded
launchctl list | grep com.family
```

### Step 2b: Create a crontab entry (for simple one-liner commands)

```bash
# Edit crontab
crontab -e

# Format: minute hour day month weekday command
# Examples:

# Every day at 6 PM
0 18 * * * /usr/bin/python3 /Users/YOURNAME/parenting/check-grades.py >> ~/Library/Logs/family-automation/grades.log 2>&1

# Every 30 minutes between 8 AM and 8 PM
*/30 8-20 * * * /usr/bin/node /Users/YOURNAME/parenting/monitor-registration.js >> ~/Library/Logs/family-automation/monitor.log 2>&1

# Every Monday at 7 AM (weekly family digest)
0 7 * * 1 /usr/local/bin/claude -p "Run the weekly family digest. Check grades, upcoming events, and lunch balances. Send ntfy summary." >> ~/Library/Logs/family-automation/digest.log 2>&1

# First of every month at 9 AM (check upcoming camps/registrations)
0 9 1 * * /usr/bin/python3 /Users/YOURNAME/parenting/find-upcoming-registrations.py 2>&1
```

### Step 2c: Use Claude Code `/schedule` (for agent workflows)

Claude Code's `/schedule` skill creates remote triggers that run Claude agents on a cron schedule, even when your Mac is asleep:

```
/schedule create "daily grade check"
  --cron "0 18 * * *"
  --prompt "Check grades for all children using the school-portal-watcher agent.
            Send ntfy notification if any grade dropped or new assignment posted."
```

This is best for workflows that need web search, API calls, and notification sending, rather than local file access.

---

## Action: `list`

Show all running family automation tasks:

```bash
echo "=== launchd agents ==="
launchctl list | grep "com.family"

echo ""
echo "=== crontab ==="
crontab -l 2>/dev/null || echo "(no crontab)"

echo ""
echo "=== log files ==="
ls -la ~/Library/Logs/family-automation/ 2>/dev/null || echo "(no log directory)"
```

---

## Action: `remove`

```bash
# launchd:
launchctl unload ~/Library/LaunchAgents/com.family.grade-check.plist
rm ~/Library/LaunchAgents/com.family.grade-check.plist

# crontab: open editor and delete the line
crontab -e
```

---

## Action: `debug`

Use this when a job isn't running or producing unexpected output.

```bash
# 1. Check if the agent is loaded and its last exit code
launchctl list | grep "com.family.grade-check"
# Output: PID  LastExitCode  Label
# 0 = success, non-zero = error

# 2. Read the error log
tail -50 ~/Library/Logs/family-automation/grade-check.err

# 3. Read stdout log
tail -50 ~/Library/Logs/family-automation/grade-check.log

# 4. Run the job manually right now (for testing)
launchctl start com.family.grade-check

# 5. Check system log for launchd messages
log show --predicate 'subsystem == "com.apple.launchd"' --last 1h | grep "grade-check"
```

**Common problems and fixes:**

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Exit code 1, script not found | Wrong path in plist | Use full absolute paths; test with `which python3` |
| Exit code 1, permission denied | Script not executable | `chmod +x script.sh` |
| Job never runs | Not loaded | `launchctl load ...plist` |
| Job runs but does nothing useful | Wrong PATH | Add PATH to EnvironmentVariables in plist |
| Python module not found | Wrong Python / missing venv | Use full path to venv Python: `/Users/YOURNAME/.venv/bin/python3` |
| Keychain access denied | Launchd doesn't have keychain access | Run script interactively once first to grant keychain access |
| Job ran during sleep, missed | Machine was asleep | Add `RunAtLoad true` to run at next wake, or use Claude Code `/schedule` for cloud-based scheduling |

---

## Action: `logs`

Stream or review logs for a job:

```bash
# Live stream (while testing)
tail -f ~/Library/Logs/family-automation/grade-check.log

# Last 100 lines with timestamps
tail -100 ~/Library/Logs/family-automation/grade-check.err

# Search for errors across all family automation logs
grep -i "error\|exception\|traceback\|failed" ~/Library/Logs/family-automation/*.err
```

---

## Recommended schedule for parenting automation

| Task | Schedule | Method |
|------|----------|--------|
| Grade check | Daily at 6 PM | launchd |
| Lunch balance check | Daily at 7 AM | launchd |
| School announcement scrape | Daily at 7 AM | launchd |
| Registration monitoring (peak season) | Every 30 min, 8 AM – 8 PM | launchd |
| Weekly family digest | Monday 7 AM | crontab or `/schedule` |
| Doctor appointment monitoring | Every 2 hours | launchd (StartInterval 7200) |
| Summer camp research | Once in February | Manual + `/find-camps` |
| Monthly: check library holds | 1st of month, 9 AM | crontab |

---

## Notification integration

All scheduled jobs should send a notification when they complete or find something actionable. Use ntfy (free, no account needed):

```bash
# From any shell script:
curl -s -d "Grade check complete: Alex has 2 new grades posted" \
  https://ntfy.sh/your-family-topic

# With priority:
curl -s \
  -H "Title: Registration Alert" \
  -H "Priority: high" \
  -d "Camp registration for Alex is now OPEN: [link]" \
  https://ntfy.sh/your-family-topic
```

```python
# From Python:
import requests
requests.post(
    'https://ntfy.sh/your-family-topic',
    data='Lunch balance for Alex is low: $8.50'.encode(),
    headers={'Title': 'Lunch Alert', 'Priority': 'default'}
)
```

Subscribe on your phone by downloading the ntfy app and subscribing to `your-family-topic`.

---

## Security notes

- **Never put passwords in plist files or crontab.** Use macOS Keychain (see `browser_automation_skill`) or environment variables loaded from a file with `chmod 600`.
- **Log files may contain sensitive data** (grade details, health info). Set log rotation and restrict permissions: `chmod 700 ~/Library/Logs/family-automation/`
- **Test scripts manually first** before scheduling — verify they produce the right output and don't have side effects (like accidentally submitting a form).

