Cron job didn't fire
Diagnose by surface

The homelab has four scheduling surfaces. The “didn’t fire” question depends on which one.

Surface Where it lives How to check
Mac launchd ~/Library/LaunchAgents/*.plist launchctl list | grep -i <name> shows last exit code; tail -50 ~/Library/Logs/<job>.log
Mac user cron crontab -l on Mac Studio tail -50 /tmp/cron-*.log if the job writes there; otherwise add MAILTO="" and re-run
CT100 cron pct exec 100 -- crontab -l Errors route to Gotify via cron-gotify-wrapper.sh (priority 5 โ†’ Telegram). Check the telegram bot.
hpve cron crontab -l on pve as root Same wrapper as CT100 โ€” errors โ†’ Gotify
Cowork scheduled tasks Cowork Settings โ†’ Scheduled Tasks lastRunAt timestamp on each task; audit log in ~/Library/Application Support/Claude/local-agent-mode-sessions/...
launchd didn't fire
# Show last exit code (column 1) and PID (column 2 โ€” - means not running)
launchctl list | grep -i com.bee

# Manually kickstart (run now)
launchctl kickstart -k gui/$(id -u)/com.bee.<job-name>

# If kickstart errors with "Could not find specified service", the plist isn't loaded:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.bee.<job-name>.plist

# If the plist has a typo, log will say so
log show --predicate 'subsystem == "com.apple.xpc.launchd"' --info --last 1h | grep com.bee.<job-name>

Common launchd gotcha: the plist points at a script that doesn’t exist (e.g., com.bee.rebuild-mcp-venvs.plist references ~/scripts/rebuild-mcp-venvs.sh โ€” make sure that file exists and is executable). The launchd job loads fine but every run silently fails.

cron didn't fire
# On CT100 or hpve โ€” list crontab
crontab -l

# The cron-gotify-wrapper.sh captures stderr to Gotify. If you don't see an
# error, the job ran cleanly OR the wrapper isn't installed.
# Verify wrapping:
crontab -l | head -10  # should see /usr/local/bin/cron-gotify-wrapper.sh <cmd>

# Validate the cron expression
# mcp__cron-validator__cron_validate("0 4 * * *")
Cowork scheduled task didn't fire
# Find today's session for the task
python3 << 'PY'
import json, os, glob, datetime
base = os.path.expanduser('~/Library/Application Support/Claude/local-agent-mode-sessions')
today = datetime.date.today().isoformat()
task = 'daily-briefing'  # change as needed
for f in glob.glob(f'{base}/*/*/local_*.json'):
    try: d = json.load(open(f))
    except: continue
    if d.get('scheduledTaskId') != task: continue
    if datetime.datetime.fromtimestamp(d['createdAt']/1000).date().isoformat() != today: continue
    print(datetime.datetime.fromtimestamp(d['createdAt']/1000), '->', datetime.datetime.fromtimestamp(d['lastActivityAt']/1000), f)
PY

If no session for today appeared, the Cowork scheduler didn’t fire โ€” check Cowork Settings โ†’ Scheduled Tasks. If a session DID appear but the expected output is missing, read its audit.jsonl to see where it got stuck.

Fix

Most often the fix is one of:

  • Re-bootstrap the launchd plist after editing
  • Re-run manually to confirm the job works, then wait for next scheduled run
  • Update the Cowork task definition (Settings โ†’ Scheduled Tasks โ†’ Edit) if the cron expression is wrong

For broken behavior despite the job firing, see the specific runbooks (doc-sync auth fail, daily briefing not arriving, dictation not processing).