è§£éããå幎ãæå0ãã120äžåãŸã§æã¡çŽããèªåŸç°å¢ãããæ8æã«é»ã£ãŠæ¢ãŸã£ãŠãã話ãããŸããåå ã¯macOSã®ã¢ããããŒãã«ããcron daemonã®ç¡å¹åã察çãšããŠæžããã®ããcrontabã1è¡ãã€è§£æããŠlaunchd plistãèªåçæãã97è¡ã®ã·ã§ã«ã¹ã¯ãªããã§ãã
ãªããã®ä»çµã¿ãå¹ãã®ã
ãåããŠããã¯ããã¯æãå±éºãªç¢ºä¿¡
坿¥ã§æ60äžã皌ãã§ããææãèªååã®æ©æµã¯ã»ãŒå
šé¡ãcronãžã§ãã«ä¹ã£ãŠããŸãããnoteã®å
¬éã¿ã€ãã³ã°èª¿æŽãSNSæçš¿ã®ã¹ã±ãžã¥ãŒãªã³ã°ãæ¥æ¬¡ã®ããŒã¿éèšââãããããã¹ãŠcrontab -lã«äžŠãã§ãããè§£éãããŠ0ã«ãªããClaude Codeã§ç°å¢ãåæ§ç¯ãããšãããcrontabããã®ãŸãŸåŒãç¶ãã®ãæéã ãšå€æããŸããã
macOS SequoiaïŒ15ç³»ïŒã«äžããçŽåŸã¯äœãå€ãã£ãŠããªãããã«èŠããŸãããcrontab -lãå©ãã°ãšã³ããªã¯å
šéšããããããdaemonã¯èµ·åããŠããªããAppleã¯macOS Ventura以éãæ®µéçã«cron daemonããŠãŒã¶ãŒã»ãã·ã§ã³ããåãé¢ããŠããŠãããSequoia/Tahoeã§ã¯/usr/sbin/cronã¯ååšããã®ã«launchctl list | grep cronã§ã¯äœãè¿ã£ãŠããªãç°å¢ãæ®éã«ãããŸãã
æ°ã¥ãã®ãé
ããã®ã¯ãèªååãæ¢ãŸã£ãŠããšã©ãŒãåºãªãããã§ããcronãåããŠããªããªã/var/mail/ãŠãŒã¶ãŒåã«ãšã©ãŒã¡ãŒã«ãæ¥ãââãšããæ³å®ã厩ããŠãããSequoideã§ã¯ããã©ã«ãã§ãã¹ããªãã£ã¹ã«å±ããªããæ8æã®ãã€ãªãŒããªãŒããæ¥ãªãã11æã®SNSæçš¿ãæµããªããããã§ããããæ°ã¥ãããã®ãéããªæ»ããæãã
launchdãæ£è§£ã§ããçç±
macOSã«ãããŠããã»ã¹ã®èµ·åã»ç®¡çãæ
ãã®ã¯launchdïŒPID 1ïŒã§ããcronã¯æŽå²çãªäºæãšããŠæ®ã£ãŠããã ãã§ãAppleãæ¬æ¥æšå¥šããã®ã¯launchdçµç±ã®ãžã§ã管çã§ããlaunchdã¯daemonãã¯ã©ãã·ã¥ãããšãã®èªååèµ·åããã·ã³ã¹ãªãŒãäžã«ã¹ã±ãžã¥ãŒã«ããããžã§ãã®ã¹ãªãŒãè§£é€åŸèªåå®è¡ãæšæºåºåã»ãšã©ãŒåºåã®ãã¡ã€ã«ãžã®çŽæ¥ãªãã€ã¬ã¯ããç°å¢å€æ°ã®æç€ºçãªæ³šå
¥ããšãã£ãæ©èœãplist 1ãã¡ã€ã«ã§å®£èšçã«æ±ããŸãã
cronã*/5 * * * * cmdãš1è¡ã§æžããã®ã«å¯Ÿããlaunchdã®plistã¯20ã30è¡ã®XMLã«ãªãââãã®åé·ããlaunchdç§»è¡ã®æå€§ã®å¿ççéå£ã§ããæäœæ¥ã§10æ¬ãæžãçŽãã®ã¯çŸå®çã§ãªããã ããã¹ã¯ãªããã§èªåçæããŸãã
å®éã«çšŒåããŠããplistã1æ¬èŠããšãæ§é ãè
ã«èœã¡ãŸãã~/Library/LaunchAgents/com.shun.daily-brief.plistã¯ä»¥äžã®æ§æã§ãïŒå®ãã¡ã€ã«ããæç²ããã¹ã¯~衚èšã«å€æïŒïŒ
<key>Label</key>
<string>com.shun.daily-brief</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>~/.nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:~/.local/bin</string>
</dict>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key><integer>8</integer>
<key>Minute</key><integer>0</integer>
</dict>
<dict>
<key>Hour</key><integer>10</integer>
<key>Minute</key><integer>30</integer>
</dict>
</array>
<key>ProgramArguments</key>
<array>
<string>~/.claude/scripts/claude-quota-guard.py</string>
<string>--job</string>
<string>com.shun.daily-brief</string>
<string>--</string>
<string>/bin/bash</string>
<string>~/.claude/scripts/daily-brief.sh</string>
</array>
<key>LowPriorityIO</key><true/>
<key>Nice</key><integer>10</integer>
<key>RunAtLoad</key><true/>
<key>StandardOutPath</key>
<string>~/.claude/logs/com.shun.daily-brief.log</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/com.shun.daily-brief.log</string>
泚ç®ç¹ã3ã€ãããŸãã
EnvironmentVariablesã®æç€ºãlaunchdã¯ãŠãŒã¶ãŒã®ã·ã§ã«èšå®ïŒ.zshrcã».bashrcïŒãèªã¿ãŸãããnvmçµç±ã§ã€ã³ã¹ããŒã«ããnodeã䜿ãã¹ã¯ãªããã¯ãlaunchd管çäžã§ã¯PATHãéã£ãŠãããnode: command not foundã§æ»ã¬ãcronããç§»è¡ãããžã§ããçªç¶åããªããªãåå ã®9å²ãããã§ããplistã«PATHãæç€ºããããšã§ãã·ã§ã«ãäœã§ããåããã€ããªã確å®ã«åŒã¹ãŸãã
StartCalendarIntervalã®é
å圢åŒã1æ¥ã«è€æ°åå®è¡ãããå Žåã<array>ã®äžã«<dict>ã䞊ã¹ãŸããdaily-briefã¯8:00ãš10:30ã®2åãcronãªã0 8,10 * * *ãšæžããŸãããlaunchdã¯æå»ããšã«èŸæžãäœãå¿
èŠãããããã®èšè¿°å·®ãèªåçæã¹ã¯ãªãããã©ããŸã§ã«ããŒããããåŸè¿°ã®èœãšã穎ã«é¢ãããŸãã
LowPriorityIOãšNiceãããã¯ã°ã©ãŠã³ããžã§ãã«I/Oåªå
床ãäžããCPUã¹ã±ãžã¥ãŒã©ã®niceå€ã10ã«èšå®ããŠããŸãããã©ã¢ã°ã©ãŠã³ãã®äœæ¥ïŒãšãã£ã¿ã»ãã©ãŠã¶ïŒãžã®åœ±é¿ãæå°åããããã®èšå®ã§ãèªåŸç°å¢ã®ãååšæãæ¶ããå²åŠã«æ²¿ã£ãŠããŸãã
äœæ¥ã§ãªãç°å¢ã«æè³ããæå³
æå120äžã®å
èš³ã¯ãèªåãæãåãããŠããéšåãã»ãŒãªããnoteé£èŒã»SNSæŽæ°ã»ããŒã¿éèšã®å€ããèªååãããŠããŸãããã®ç°å¢ã®ç¶æã³ã¹ãã¯ãcronãæ£ããåãåºç€ã«ç§»ãæ¿ããããšã«å°œããŸãã1床æžãã宿ã¿ã¹ã¯ã3幎åŸãåããŠããã®ãlaunchd管çäžã®ãŽãŒã«ã§ããAppleã®launchdã¯macOS 10.4ïŒ2005幎ïŒããå€ãããªãå®å®ããAPIã§ãcronã®ããã«ãæ°ã¥ããæ»ã¬ãããšããªããlaunchctl list com.shun.daily-briefã§å³åº§ã«LastExitStatusãšæ¬¡åå®è¡äºå®ã確èªã§ããŸãã
ç°å¢ãæŽãã90åã¯ãæ¯æãã¡ãããšåããŠããã確èªããã5åÃ365æ¥ïŒ=30æéïŒãè²·ãæ»ãæè³ã§ãã
å šäœã®æµã
åŠçã®å°å³
crontab -l
â grep -vE '^\s*#' | grep -v '^$' â ã³ã¡ã³ãè¡ã»ç©ºè¡ãé€å€
â
[1è¡ããšã«ã«ãŒã]
â awk '{print $1...$5}' ã§ schedule ãã£ãŒã«ãæœåº
â cut -d' ' -f6- ã§ cmd éšåãåãåºã
â basename ããã©ãã«çæ â com.shun.<script-name>
â
StartCalendarInterval XML çµã¿ç«ãŠ
â â» */N 圢åŒã¯é察å¿ïŒåºå®å€ã®ã¿ïŒâ ãããèœãšã穎
â
plist ãã¡ã€ã«æžãåºã
â [dry] ~/.claude/scripts/launchd-proposed/*.plist
â [apply] ~/Library/LaunchAgents/*.plist
+ launchctl unload â launchctl load
â
â ïž èŠå: crontab ããæååé€ããªããšäºéèµ·å
ã¹ã¯ãªããè§£åïŒå š97è¡ïŒ
ã¹ã¯ãªããã¯~/.claude/scripts/cron-to-launchd.shã«çœ®ããŠãããäœ¿ãæ¹ã¯2éãã§ãã
# å·®å確èªïŒãã¡ã€ã«ãæžãã ããloadããªãïŒ
~/.claude/scripts/cron-to-launchd.sh dry
# æ¬çªåæ ïŒLaunchAgentsã«ã³ããŒããŠlaunchctl loadïŒ
~/.claude/scripts/cron-to-launchd.sh apply
åŒæ°ãªãã§åŒã¹ã°dryãããã©ã«ãïŒMODE="${1:-dry}"ïŒããããªãapplyããããŸãdryã§çæç©ãç®èŠç¢ºèªããã®ãéåã§ãã
ãã§ãŒãº1ïŒcrontabã®èªã¿èŸŒã¿ãšããŒã¹ïŒ20ã28è¡ç®ïŒ
CRON_LINES=()
while IFS= read -r line; do
[ -n "$line" ] && CRON_LINES+=("$line")
done < <(crontab -l 2>/dev/null | grep -vE '^\s*#' | grep -v '^$')
bash 3.2 äºæãšã³ã¡ã³ãã«ããéããmacOSæšæºã®bashã¯ããŒãžã§ã³3.2ïŒGPLv2ã®éœåã§AppleãæŽæ°ããŠããªãïŒã§ããmapfileãreadarrayã¯3.2ã§ã¯äœ¿ããªããããwhile IFS= read -rã®ã«ãŒãã§é
åãæ§ç¯ããŠããŸããcrontab -l 2>/dev/nullã§crontabã空ã®å Žåã®ãšã©ãŒãæ¡ãã€ã¶ããgrep -vE '^\s*#'ã§ã³ã¡ã³ãè¡ãgrep -v '^$'ã§ç©ºè¡ãé€å»ããŸãã
ãã§ãŒãº2ïŒåè¡ãã schedule ãš cmd ãåè§£ïŒ28ã38è¡ç®ïŒ
minute=$(echo "$line" | awk '{print $1}')
hour=$(echo "$line" | awk '{print $2}')
dom=$(echo "$line" | awk '{print $3}')
mon=$(echo "$line" | awk '{print $4}')
dow=$(echo "$line" | awk '{print $5}')
cmd=$(echo "$line" | cut -d' ' -f6-)
cronã®æžåŒmin hour dom mon dow cmd...ãawkã§1ãã£ãŒã«ããã€åãåºããŸããcmdã¯6çªç®ä»¥éããã¹ãŠcut -d' ' -f6-ã§åããããã³ãã³ãã«åŒæ°ãããã€ãã£ãŠãæ£ããæŸããŸãã
ã©ãã«ã®çæããžãã¯ïŒ38ã40è¡ç®ïŒïŒ
script=$(echo "$cmd" | grep -oE '~/.claude/scripts/[^ ]+' | head -1 | xargs basename 2>/dev/null)
if [ -z "$script" ]; then
script="$(echo "$cmd" | awk '{print $1}' | xargs basename 2>/dev/null)-${minute}${hour}"
fi
label="com.shun.$(echo "$script" | sed -E 's/\.[a-z]+$//' | tr '_' '-')"
~/.claude/scripts/é
äžã®ã¹ã¯ãªããã¯basenameããæ¡åŒµåãé€ããŠã©ãã«åããŸããããšãã°daily-brief.shãªãcom.shun.daily-briefããã以å€ã®æ±çšã³ãã³ãïŒfindã»backup-rotateãªã©ïŒã¯ãã³ãã³ãåïŒåïŒæã§ãŠããŒã¯æ§ã確ä¿ããŸããã¢ã³ããŒã¹ã³ã¢ã¯ãã€ãã³ã«å€æïŒlaunchã®LabelèŠçŽïŒã
ãã§ãŒãº3ïŒStartCalendarInterval XMLã®çµã¿ç«ãŠïŒ44ã52è¡ç®ïŒ
cal_xml=" <key>StartCalendarInterval</key>\n <dict>\n"
# */N åšæã¯ launchd ã§ã¯è€æ°ãšã³ããªã§åçŸããå¿
èŠ â ããã§ã¯åºå®å€ã ã察å¿
if [ "$minute" != "*" ]; then cal_xml+=" <key>Minute</key><integer>${minute}</integer>\n"; fi
if [ "$hour" != "*" ]; then cal_xml+=" <key>Hour</key><integer>${hour}</integer>\n"; fi
if [ "$dom" != "*" ]; then cal_xml+=" <key>Day</key><integer>${dom}</integer>\n"; fi
if [ "$mon" != "*" ]; then cal_xml+=" <key>Month</key><integer>${mon}</integer>\n"; fi
if [ "$dow" != "*" ]; then cal_xml+=" <key>Weekday</key><integer>${dow}</integer>\n"; fi
cal_xml+=" </dict>"
ãã£ãŒã«ãã*ïŒã¯ã€ã«ãã«ãŒãïŒãªã察å¿ããããŒãXMLã«å«ããªãââãããlaunchdã®StartCalendarIntervalã®æå³è«ã§ããããšãã°0 8 * * *ïŒæ¯æ¥8æ0åïŒã¯Hour=8, Minute=0ã®ã¿æžãã°ãããDay/Month/Weekdayã¯çç¥ããããšã§ãæ¯æ¥ãã«ãªããŸãã
ãã§ãŒãº4ïŒplistæ¬äœã®æžãåºãïŒ54ã76è¡ç®ïŒ
cat > "$plist" <<XMLEOF
<?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>${label}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>-c</string>
<string>${cmd//&/&}</string>
</array>
$(echo -e "${cal_xml}")
<key>StandardOutPath</key>
<string>${log}</string>
<key>StandardErrorPath</key>
<string>${log}</string>
<key>ProcessType</key>
<string>Background</string>
</dict>
</plist>
XMLEOF
ã³ãã³ãã¯/bin/zsh -c "cmd"ãšããŠã©ããããŸããcronã§èµ°ã£ãŠããã³ãã³ãã¯ã·ã§ã«ã®å±éïŒ~å±éã»ã°ããïŒã«äŸåããŠããããšãå€ããProgramArgumentsã«çŽæ¥æž¡ããšåããªãã±ãŒã¹ããããzshçµç±ã«ããããšã§ãã®å·®ç°ãåžåããŠããŸãã${cmd//&/&}ã¯XMLãšã¹ã±ãŒãââã³ãã³ãã«&ãå«ãŸãããšäžæ£ãªXMLã«ãªããããããã§çœ®æããŸãããã°ã¯~/.claude/logs/${label}.logã«æšæºåºåã»ãšã©ãŒåºåããŸãšããŠåããŸãã
ãã§ãŒãº5ïŒapplyã¢ãŒãã®é åïŒ84ã96è¡ç®ïŒ
if [ "$MODE" = "apply" ]; then
for f in "$PROPOSED"/*.plist; do
cp "$f" "$TARGET_DIR/"
launchctl unload "$TARGET_DIR/$(basename $f)" 2>/dev/null
launchctl load "$TARGET_DIR/$(basename $f)"
echo " loaded: $(basename $f)"
done
echo ""
echo "ðš cron è¡ã¯ **æåã§åé€ããŠãã ãã**: crontab -e"
echo "(誀ã£ãŠ cron+launchd äž¡æ¹èµ°ãã®ãé¿ãããã)"
fi
launchctl unloadãå
ã«åŒã¶ã®ã¯åªçæ§ã®ããã§ãããã§ã«loadãããŠããplistã«å床loadããããšãããšãšã©ãŒã«ãªããäºåã«unloadããŠããããšã§ãapplyãäœåºŠå®è¡ããŠãåãçµæã«ãªããŸãããã ãæ³šæç¹ã1ã€ââã¹ã¯ãªããã¯applyåŸã«ãcrontabããæååé€ããŠãã ããããšèŠåãåºãã ãã§ãåé€ã¯èªååããŠããŸãããcronè¡ãæ®ãããŸãŸæŸçœ®ãããšãmacOSãå°æ¥çã«cron daemonã埩掻ãããã¿ã€ãã³ã°ã§cron+launchdã®äºéèµ·åãçºçããŸãã
å®è£ ã®è©³çް
set -uo pipefail ââ -e ãå€ããçç±
ã¹ã¯ãªãã1è¡ç®ã®å®£èšã¯ set -uo pipefail ã§ãïŒå®ãã¡ã€ã«9è¡ç®ïŒã-eïŒãšã©ãŒå³çµäºïŒãå
¥ã£ãŠããªãããšã«æ°ã¥ãã人ããããããããŸãããããã¯æå³çãªèšèšã§ãã
applyã¢ãŒãã®ã«ãŒããèŠãŠãã ããïŒ84ã96è¡ç®ïŒã
launchctl unload "$TARGET_DIR/$(basename $f)" 2>/dev/null
launchctl load "$TARGET_DIR/$(basename $f)"
launchctl unloadã¯ã察象ã®plistããŸã loadãããŠããªããã°çµäºã³ãŒãéãŒããè¿ããŸãã-eãæå¹ãªãã1æ¬ç®ã®ååunloadã§ã¹ã¯ãªãããæ»ã¬ã2>/dev/nullã§ãšã©ãŒåºåã¯æ¶ããŠããŸãããçµäºã³ãŒãã¯æ®ããŸãã-eãªãã«ããããšã§ãunloadã倱æããŠãã«ãŒããç¶ããããšããåªçãªæåãå®çŸããŠããŸãã
åãçç±ã§ãcrontab -l 2>/dev/nullïŒ22è¡ç®ïŒãå®å
šã§ããcrontabã空ã®ãŠãŒã¶ãŒç°å¢ã§ã¯crontab -lãcrontab: no crontab for ãŠãŒã¶ãŒåãšéãŒãã§çµãããŸããã2>/dev/nullã§æ¡ãã€ã¶ããŠã«ãŒãã«é²ããŸãã-eãããã°ããã§èœã¡ãŠããã
-uïŒæªå®çŸ©å€æ°ãšã©ãŒïŒãš-o pipefailïŒãã€ã倱æã®äŒæïŒã¯æ®ãã ããã¯å¿
èŠãªã¬ãŒãã§ãã倿°åã®ã¿ã€ããããã€ãéäžã§ã®å€±æãæŸãããã§ãã-eã ããè¶³ãåŒã£åŒµãç¶æ³ââãã®å€æã¯ã·ã§ã«ã¹ã¯ãªããã®ãšã©ãŒãã³ããªã³ã°ã§é »åºãããã¿ãŒã³ã§ãã
ã©ãã«çæã®æ£èŠè¡šçŸãšçµ¶å¯Ÿãã¹äŸå
35è¡ç®ã®ã©ãã«çæããžãã¯ãæ£ç¢ºã«èªããšã1ã€éèŠãªä»æ§ãèŠããŸãã
script=$(echo "$cmd" | grep -oE '~/.claude/scripts/[^ ]+' | head -1 | xargs basename 2>/dev/null)
æ£èŠè¡šçŸã~/.claude/scripts/ã§ã¯ãªã絶察ãã¹ã§ãããããŠããããšã«æ³šç®ããŠãã ãããcrontabã®ãšã³ããªã~/.claude/scripts/daily-brief.shãšæžãããŠããå Žåããã®æ£èŠè¡šçŸã«ã¯ãããããŸããã~ã¯ã·ã§ã«ãå±éããåã®æååãšããŠèšé²ãããŠããããã§ãããããããªããã°script倿°ã¯ç©ºã«ãªãããã©ãŒã«ããã¯ã«èœã¡ãŸãã
if [ -z "$script" ]; then
script="$(echo "$cmd" | awk '{print $1}' | xargs basename 2>/dev/null)-${minute}${hour}"
fi
ãã©ãŒã«ããã¯ã¯ãã³ãã³ãåã®basenameïŒåïŒæãã§ããããšãã°~/.claude/scripts/daily-brief.shã0 8 * * *ã§ç»é²ããŠãããªããã©ãã«ã¯com.shun.daily-brief-08ã«ãªããŸããdaily-briefã§ã¯ãªãdaily-brief-08ããã®ãºã¬ãåŸã§ãã°ã远ãããããšãã«æ··ä¹±ãçã¿ãŸãã
crontabã«ç»é²ãããšãã¯åžžã«çµ¶å¯Ÿãã¹ã§æžããããããã®ã¹ã¯ãªãããšå ±åããå¯äžã®æ£è§£ã§ãã
*/N 圢åŒãå£ããä»çµã¿ââã³ãŒãã§è¿œã
ååã§è§Šããã*/Né察å¿ãããå®éã®ã³ãŒããããŒã§ç¢ºèªããŸããããšãã°crontabã«*/15 * * * * ~/.claude/scripts/health-check.shãšããè¡ããã£ãå Žåãäœãèµ·ãããã
minute=$(echo "*/15 * * * * ~/.claude/scripts/health-check.sh" | awk '{print $1}')
# â "*/15"
æ¬¡ã«æ¡ä»¶åå²ïŒ
if [ "$minute" != "*" ]; then
cal_xml+=" <key>Minute</key><integer>${minute}</integer>\n"
fi
"*/15" != "*"ã¯trueãªã®ã§æ¡ä»¶ãééããçæãããXMLã¯ïŒ
<key>Minute</key><integer>*/15</integer>
<integer>ã¿ã°ã®äžã«*/15ãšããæååãå
¥ããŸããXMLãšããŠã¯äžå¿ããŒã¹ã§ããŸãããlaunchdãplistãããŒããããšãã«ãMinuteã¯0ã59ã®æŽæ°ã§ãªããã°ãªããªãããšããæ€èšŒã§è¹ŽãããŸããlaunchctl loadã¯çµäºã³ãŒãéãŒããè¿ããloaded:ã®è¡šç€ºã¯ãããŸãããå®éã«ã¯ã¹ã±ãžã¥ãŒãªã³ã°ãæå¹ã«ãªã£ãŠããŸããã
ãã®ãloadã¯éã£ãããã«èŠãããå®ã¯åããŠããªããç¶æ ãåä»ã§ã次ã®ç¯ã§ãç»å ŽããŸãã
/bin/zsh -c ã©ããã®æå³ãšéç
çæãããplistã®ProgramArgumentsã¯ïŒ54ã66è¡ç®ïŒïŒ
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>-c</string>
<string>${cmd}</string>
</array>
zshã§ã³ãã³ããã©ããããã®ã¯ãcronãšã³ããªã«å«ãŸããã¡ãª~å±éã»ç°å¢å€æ°åç
§ã»ã°ãããã¿ãŒã³ãè§£éãããããã§ããProgramArgumentsã«ã³ãã³ããçŽæ¥æž¡ããšãã·ã§ã«å±éãªãã§execvpãåŒã°ãããã~ããã®ãŸãŸæååãšããŠæž¡ãããŠãã¡ã€ã«ãèŠã€ãããªããšã©ãŒã«ãªããŸãã
ãã ãã/bin/zsh -cã§ãlaunchdã¯ãŠãŒã¶ãŒã®.zshrcãèªã¿ãŸãããããã¯launchdã®èšèšã§ããzshã®interactiveã¢ãŒãã§ã¯ãªãnon-loginã®scriptã¢ãŒãã§èµ·åãããããsource ~/.zshrcãæžããŠããŠãèªã¿èŸŒãŸããªããçµæãšããŠnvmã§ç®¡çããŠããnodeã»pyenvã®pythonã»Homebrewã®åçš®ã³ãã³ãã¯PATHãéã£ãŠããªãç¶æ
ã§èµ·åããŸãã
çæplistã®ãã³ãã¬ãŒããèŠããšãEnvironmentVariablesããŒããããŸããïŒ54ã76è¡ç®å
šäœãèŠãŠããªãïŒãdaily-brief.plistãæäœæ¥ã§EnvironmentVariablesã远èšããŠããã®ã¯ãã®ããã§ããã¹ã¯ãªãããèªåçæããplistã«ã¯ããã®PATH泚å
¥ãå«ãŸããŠããŸããã
ç§ãè©°ãŸã£ã話
è©°ãŸã1ïŒ*/15 * * * *ãsilent failãã
çç¶ã applyãå®è¡ãããšloaded: com.shun.health-check.plistãšè¡šç€ºããããããã15ååŸã30ååŸã~/.claude/logs/com.shun.health-check.logã«ã¯äœãæžãããªãã
launchctl list com.shun.health-check
# â Could not find service "com.shun.health-check" in domain for port
loadãããŠããã¯ãã®ãµãŒãã¹ããlaunchctlã®ãªã¹ãã«ååšããªãã
åå ã */15ããã®ãŸãŸ<integer>*/15</integer>ã«æžãããlaunchdãplistã®æ€èšŒã§å
éšçã«æ£åŽããŠããŸãããlaunchctl loadã³ãã³ãèªäœã¯çµäºã³ãŒã0ãè¿ããããïŒmacOS Sequoiaã§ã®æåïŒãã¹ã¯ãªããã®echo "loaded:"ãèµ°ã£ãŠããŸã£ãããšã©ãŒã衚瀺ãããªããŸãŸããµãŒãã¹ã¯ååšããªãç¶æ
ã«ãªã£ãŠããŸããã
çŽãæ¹ã plutil -lint ~/.claude/scripts/launchd-proposed/com.shun.health-check.plistã§plistãæ€èšŒãããšå³åº§ã«åŒŸãããŸãã*/15ãå«ãè¡ã¯ç§»è¡åã«crontabããæåã§æžãæããå¿
èŠã§ãã15åãããªãlaunchdã®StartIntervalïŒç§åäœã®ééæå®ïŒã«å€ãããã00,15,30,45ãšå±éããåºå®å€ã4ãšã³ããªã®é
åãšããŠæžããŸãã
<key>StartCalendarInterval</key>
<array>
<dict><key>Minute</key><integer>0</integer></dict>
<dict><key>Minute</key><integer>15</integer></dict>
<dict><key>Minute</key><integer>30</integer></dict>
<dict><key>Minute</key><integer>45</integer></dict>
</array>
ãããã¯StartIntervalã§ç§æå®ããæ¹ãã·ã³ãã«ã§ãïŒ
<key>StartInterval</key>
<integer>900</integer>
900ç§ïŒ15åããã®åœ¢åŒãªãã¹ã¯ãªããã®èªåçæç¯å²å€ã§ãããææžã1ç®æã§æžã¿ãŸãã
è©°ãŸã2ïŒcrontabã®~ãã¹ã§ã©ãã«ãè¡çªãå€ãplistãäžæžã
çç¶ã dryã¢ãŒãã§ç¢ºèªãã~/.claude/scripts/launchd-proposed/ã®äžã«ãèŠèŠãã®ãªãã©ãã«åã®plistãçãŸããŠãããcom.shun.daily-brief-08.plistãšcom.shun.note-publish-308.plistãªã©ãæ«å°Ÿã«æå»ãä»ãåœåã
åå ã crontabã«~/.claude/scripts/daily-brief.shãš~ã§æžããŠããããã35è¡ç®ã®çµ¶å¯Ÿãã¹ããã~/.claude/scripts/[^ ]+ã«ãããããããã©ãŒã«ããã¯ã®ã³ãã³ãå-åæåœ¢åŒã«ãªã£ãŠããŸãããããã«ã以åã«applyãããšãã«çæããcom.shun.daily-brief.plistã¯~/Library/LaunchAgents/ã«æ®ã£ãŠãããããå€ãplistãšæ°ããplistãå¥ã©ãã«ã§äºéã«ååšããç¶æ
ã«ãªã£ãŠããŸããã
launchctl list | grep com.shunãå©ããšåãã¹ã¯ãªãããåŒã¶ãšã³ããªã2ã€è¡šç€ºãããŠããŸããã
çŽãæ¹ã crontabãcrontab -eã§éãã~ã絶察ãã¹ã«æžãçŽããŸããæ¬¡ã«~/Library/LaunchAgents/ã®æ§ã©ãã«plistãæåã§unloadããŠåé€ããŸãã
launchctl unload ~/Library/LaunchAgents/com.shun.daily-brief-08.plist
rm ~/Library/LaunchAgents/com.shun.daily-brief-08.plist
applyã®åã«å¿
ãdryã§çæã©ãã«ãç®èŠç¢ºèªããæåŸ
ããcom.shun.<ã¹ã¯ãªããå>圢åŒã«ãªã£ãŠããããèŠãç¿æ
£ãå¿
èŠã§ãããã©ãŒã«ããã¯åœ¢åŒïŒæ«å°Ÿã«æ°åïŒãæ··ãã£ãŠããããcrontabã®æžãæ¹ãçããŸãã
è©°ãŸã3ïŒnodeã»pythonãcommand not found
çç¶ã applyåŸã~/.claude/logs/com.shun.note-autolike.logã«æ¯ååããšã©ãŒãåºç¶ããã
/bin/zsh: node: command not found
æåã§ã¿ãŒããã«ããåãã³ãã³ããå®è¡ãããšåé¡ãªãåãã
åå ã çæplistã«EnvironmentVariablesãå«ãŸããªãããã§ãã/bin/zsh -cã§èµ·åããŠã.zshrcã¯èªãŸãããnvmã远å ãã~/.nvm/versions/node/v24.13.0/binã¯PATHã«ååšããªããã¿ãŒããã«ã®ã·ã§ã«ã»ãã·ã§ã³ãšãlaunchd管çäžã®ããã»ã¹ã¯ãŸã£ããå¥ã®PATHç°å¢ã§åããŸãã
çŽãæ¹ã çæãããplistãæåã§ç·šéãã<key>ProgramArguments</key>ã®åã«EnvironmentVariablesã远å ããŸããdaily-brief.plistïŒå®ãã¡ã€ã«ããåŒçšïŒãæ£è§£ã®ã¢ãã«ã§ãïŒ
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>~/.nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:~/.local/bin</string>
</dict>
æ¬æ¥ã¯ã¹ã¯ãªããã®çæãã³ãã¬ãŒãã«ãã®ãããã¯ãçµã¿èŸŒãã¹ãã§ãããã ããã©ã®nodeããŒãžã§ã³ã䜿ãããã¯ç°å¢ã«ãã£ãŠç°ãªãããããã³ãã¬ãŒãã«ããŒãã³ãŒããããšç°å¢ãå€ãã£ããšãã«å šplistã®æžãçŽããå¿ èŠã«ãªããçŸåšã®ã¹ã¯ãªãããæå³çã«çããŠããã®ã¯ããã®ãããŒãžã§ã³åºå®ã®å±ããããé¿ãããããããããŸããââå°ãªããšãèªåã§ããè§£éããŠæãåããã€ããŠããŸãã
å®éã®éçšã§ã¯ãnodeã䜿ããžã§ãã®plistã«ã¯å¿ ãæåã§EnvironmentVariablesãæžãè¶³ããŠããŸããã¹ã¯ãªããçæã¯ãé圢ã®9å²ãäœãããšããæ®ã1å²ã®PATHæ³šå ¥ã¯æåããšãã忥ã§ãã
è©°ãŸã4ïŒcron+launchdãåäžã¹ã¯ãªãããäºéèµ·åããŠãã
çç¶ã noteèªåæçš¿ã1æ¥2åèµ°ãã¯ããããã°ãèŠããšæçš¿APIã1æ¥4ååŒã°ããŠãããã¬ãŒãå¶éã«åŒã£ãããããšã©ãŒã¬ã¹ãã³ã¹ãåºå§ããŠçºèŠããŸããã
åå ã applyåŸã«crontab -eã§cronè¡ãåé€ããã®ãå¿ããŠããŸãããã¹ã¯ãªããæ«å°ŸïŒ94ã95è¡ç®ïŒã®èŠåãèŠèœãšããŠããã®ã§ãã
ðš cron è¡ã¯ **æåã§åé€ããŠãã ãã**: crontab -e
(誀ã£ãŠ cron+launchd äž¡æ¹èµ°ãã®ãé¿ãããã)
macOS Sequoidaç°å¢ã§ã¯cron daemonãèµ·åããŠããªããããcronè¡ããã£ãŠãå®å šããšæã蟌ãã§ããŸãããå®éã«ã¯Sequoideã§ãcron daemonãåèµ·åããã¿ã€ãã³ã°ïŒäž»ã«OSã¢ããããŒãåŸïŒãããããã®ãšãäž¡æ¹ãåãå§ããŸããä»åã¯macOSã®ãã€ããŒã¢ããããŒãããã®ã¿ã€ãã³ã°ã§ããã
çŽãæ¹ã crontab -lã§launchdç§»è¡æžã¿ã®è¡ã確èªããå
šåé€ãããç§»è¡æžã¿è¡ãã³ã¡ã³ãã¢ãŠãããŸããäžçªå®å
šãªã®ã¯crontab -rïŒå
šåé€ïŒã§ãããç§»è¡æŒãããããšåãè¿ããã€ããªãããã1è¡ãã€ç¢ºèªããªããcrontab -eã§å¯ŸåŠããŸããã
ãã®å€±æä»¥æ¥ãapplyã®å®äºç¢ºèªãšããŠæ¬¡ã®2ã³ãã³ããã»ããã§èµ°ãããŠããŸãã
# launchdåŽã®çšŒå確èª
launchctl list | grep com.shun
# cronåŽã®æ®éªžç¢ºèªïŒ0è¡ãªãOKïŒ
crontab -l 2>/dev/null | grep -vE '^\s*#' | grep -v '^$' | wc -l
2ã€ç®ã®ã³ãã³ãã0ãè¿ãã°ãæå¹ãªcronè¡ã¯ååšããªãããããç§»è¡å®äºã®å€æåºæºã§ãã
è©°ãŸã5ïŒdryã§ç¢ºèªããplistãšapplyã§é
åãããplistãå¥ç©ã ã£ã
çç¶ã dryã§çæç©ãç®èŠç¢ºèªâåé¡ãªãâapplyãå®è¡âãªããã¹ã±ãžã¥ãŒã«ãå€ãã£ãŠããã
åå ã PROPOSEDãã£ã¬ã¯ããªïŒ~/.claude/scripts/launchd-proposed/ïŒã«åådryããå€ãplistãæ®ã£ãŠããŸãããä»ådryã§ã¯å¥ã®cronè¡ããã®çæãèµ°ã£ããããæŽæ°ãããplistãšå€ãplistãæ··åšããŠããç¶æ
ã«ãªã£ãŠããŸãããapplyã¯PROPOSED/*.plistãå
šéšãããã€ãããããæå³ããŠããªãå€ãäžä»£ã®plistã~/Library/LaunchAgents/ã«äžæžãã³ããŒãããŸããã
for f in "$PROPOSED"/*.plist; do
cp "$f" "$TARGET_DIR/"
ãã®å šã³ããŒãåé¡ã®èµ·ç¹ã§ãã
çŽãæ¹ã dryã®åã«PROPOSEDãã£ã¬ã¯ããªãäžåºŠã¯ãªã¢ããç¿æ
£ãã€ããŸãã
rm -f ~/.claude/scripts/launchd-proposed/*.plist
~/.claude/scripts/cron-to-launchd.sh dry
ãããã¯applyã®çŽåã«diffã§PROPOSEDãšLaunchAgentsã®å·®åã確èªãããã©ã¡ããæéã§ãããã¹ã¯ãªããåŽã«ã¯ãªãŒã³åŠçãå
¥ã£ãŠããªããããçŸç¶ã¯æåã®èŠåŸã§è£ããããããŸããã
ãããŸã§ã®è©°ãŸãããŸãšãããšãã¹ã¯ãªãããèªååããŠãããã®ã¯ãåºå®ã¹ã±ãžã¥ãŒã«ã»çµ¶å¯Ÿãã¹ã»PATHäžèŠããªè¡ã ãã§ããæ®ãââ*/N圢åŒã»~ãã¹ã»nvm/pyenväŸåââã¯æåã§ååŠçãŸãã¯åŸåŠçãå¿
èŠã§ãããã®å¢çãæåã«ææ¡ããŠããã°ã4ã€ã®å€±æã®ãã¡3ã€ã¯é²ããŸããã97è¡ã®ã¹ã¯ãªããã¯ãcronã®ç§»è¡ãå
šèªåã«ããããã®ã§ã¯ãªãããæäœæ¥ã®8å²ãã¹ãããããŠæ®ã2å²ãæµ®ã圫ãã«ãããããŒã«ãšããŠèªãã®ãæ£ç¢ºã§ãã
ã€ãŸãããã€ã³ã
åæ®µã®ãè©°ãŸã£ã話ãã§ã¯5æ¬ã®ãšããœãŒããåãäžããŸãããããã§ã¯åã倱æãç¹°ãè¿ããªãããã«ãèœãšã穎ãäœç³»çã«æŽçããŸãããŸããã¹ã¯ãªãããèªååã§ããç¯å²ãã確èªããŠãããèŠèœãšããããçœ ãäžæ°ã«äžŠã¹ãŸãã
èªååã§ããç¯å²ã»ã§ããªãç¯å²
cron-to-launchd.shïŒ97è¡ïŒãæ£ããæ©èœããã®ã¯ãæ¬¡ã®æ¡ä»¶ããã¹ãŠæºããcronè¡ã ãã§ãã
- cronåŒãåºå®å€ã®ã¿ââ
*/N圢åŒãå«ãŸãªã - ã³ãã³ãã絶察ãã¹ã§èšè¿°ãããŠããââ
~å±éã§ã¯ãªã - ã³ãã³ãæååã«
&ã»<ã»>ãå«ãŸããªã - åäžã¹ã¯ãªããã1æå»ã«ã®ã¿ç»é²ãããŠãã
ãã®4æ¡ä»¶ãå€ããè¡ã¯ãèªåçæãå£ããããçæåŸã®æåä¿®æ£ãå¿ é ã§ãããcrontabã®å šè¡ãå šèªåã§ç§»è¡ããããã®ã§ã¯ãªããã4æ¡ä»¶ãæºããè¡ã®é圢ã8å²äœããæ®ã2å²ã®æäœæ¥ãæµ®ã圫ãã«ããããŒã«ããšããŠäœ¿ãã®ãæ£ç¢ºã§ãã
èœãšã穎ãªã¹ãïŒå®ã³ãŒãä»ãïŒ
XMLãšã¹ã±ãŒãã&ã®ã¿ââ<ãš>ãå«ãè¡ã§plistãç Žæãã
ã¹ã¯ãªãã65è¡ç®ãèŠãŠãã ããã
<string>${cmd//&/&}</string>
&ã&ã«å€æããŠããŸããã<â<ã>â>ã®å€æã¯ãããŸãããcrontabã«cmd > /dev/null 2>&1ã®ãããªãªãã€ã¬ã¯ãä»ãã®è¡ãããå Žåãçæãããplistã®<string>ã¿ã°å
ã«>ãæ··å
¥ããXMLããŒãµãŒãplistãèªããªããªããŸããlaunchctl loadã¯ãšã©ãŒãè¿ããŸãããapplyã®ã«ãŒãå
ã§ã¯æ¬¡ã®plistã«é²ãããã1æ¬ã®ç ŽæãèŠèœãšããããæ§é ã§ãã>ã<ãå«ãè¡ã¯ç§»è¡åã«ãªãã€ã¬ã¯ããã¹ã¯ãªããå
éšã«ç§»ãããplistãææžãããŠãã ããã
çæplistã«RunAtLoadããªãââapplyçŽåŸã®åäœç¢ºèªãã§ããªã
èªåçæãã³ãã¬ãŒãïŒ54ã76è¡ç®å
šäœïŒã«RunAtLoadããŒã¯ååšããŸãããäžæ¹ãæåã§ä»äžããcom.shun.daily-brief.plistã®å®ãã¡ã€ã«28ã29è¡ç®ã«ã¯<key>RunAtLoad</key><true/>ãå
¥ã£ãŠããŸãã
RunAtLoadããªãplistã¯ã次ã®ã¹ã±ãžã¥ãŒã«æå»ãŸã§å®è¡ãããŸãããapplyçŽåŸã«ãã°ã確èªããŠãäœãæžãããŠããªãç¶æ
ã¯åäœäžè¯ã§ã¯ãªãèšèšéãã§ãããã ããæ¬åœã«åãã®ããããã®å Žã§ãã¹ãã§ããªããšããåé¡ããããŸãã確èªãããå Žåã¯launchctl kickstartã䜿ããŸãïŒ
launchctl kickstart -k gui/$(id -u)/com.shun.xxx
tail -f ~/.claude/logs/com.shun.xxx.log
StartCalendarIntervalã<dict>åäœââè€æ°æå»ã¯æåã§<array>åãå¿
èŠ
çæã¹ã¯ãªããã®46ã52è¡ç®ã®cal_xmlã¯<dict>1ã€ã§å®çµããŸããcom.shun.daily-brief.plistïŒå®ãã¡ã€ã«33ã47è¡ç®ïŒã®ããã«ã8:00ãš10:30ã®2åãã衚çŸããã«ã¯é
åãå¿
èŠã§ãããã¹ã¯ãªããã¯é
åãçæããŸããã
<!-- èªåçæç©ïŒåäžæå»ãã衚çŸã§ããªãïŒ -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>8</integer>
<key>Minute</key><integer>0</integer>
</dict>
è€æ°æå»ãåäžã¹ã¯ãªããã«å²ãåœãŠããå Žåã¯ãçæåŸã«plistãæåã§é å圢åŒã«æžãçŽããŸãã
ååã¹ã¯ãªãããè€æ°æå»ã§crontabã«ç»é²ãããšåŸè ã®plistãåè ãäžæžã
daily-brief.shã8æãš10æåã«åãããããŠcrontabã«2è¡æžãããšããŸãã
0 8 * * * /path/to/.claude/scripts/daily-brief.sh
30 10 * * * /path/to/.claude/scripts/daily-brief.sh
ã©ãã«çæïŒ40è¡ç®ïŒã¯ã¹ã¯ãªããåããæ¡åŒµåãé€ããããäž¡è¡ãšãcom.shun.daily-briefã«ãªããŸããplistãã¡ã€ã«åãcom.shun.daily-brief.plistã§åäžãåŸããåŠçãããè¡ïŒ10:30ã®è¡ïŒãåã®è¡ïŒ8:00ïŒãäžæžããã8æã®èšå®ãæ¶ããŸããã¹ã¯ãªããåŽã«è¡çªæ€ç¥ã¯ãããŸãããdryã§çæç©ãç®èŠç¢ºèªãããããããŸããã
*/N圢åŒãsilent failããââlaunchctl loadã¯æåã«èŠãã
p2ã§è©³è¿°ãããšããœãŒãã®æ žå¿ãäžè¡ã§ãŸãšããŸãã*/15ã<integer>*/15</integer>ãšããŠæžãåºãããlaunchdãå
éšæ€èšŒã§plistãæ£åŽããŸããlaunchctl loadã®ã³ãã³ãã¯çµäºã³ãŒã0ãè¿ãããæåã«èŠããŸãããlaunchctl list com.shun.xxxã§ãµãŒãã¹ãèŠã€ãããªããã°æ£åŽãããŠããŸããç§»è¡åã«*/Nãå«ãè¡ãæåã§å€æããããšãå¯äžã®è§£æ±ºçã§ãã
~ãã¹ã¯ã©ãã«çæã®ãã©ãŒã«ããã¯ã«èœã¡ã
35è¡ç®ã®æ£èŠè¡šçŸã¯çµ¶å¯Ÿãã¹ã®ã¿ãããããŸãã~/.claude/scripts/note-autolike.shãšæžããŠããã°ããã©ãŒã«ããã¯ïŒ36ã39è¡ç®ïŒãåããŠã©ãã«ãcom.shun.note-autolike-308ã®ãããªæ«å°Ÿæ°åä»ãã«ãªããŸãã以åã«çµ¶å¯Ÿãã¹ã§çæããcom.shun.note-autolike.plistã~/Library/LaunchAgents/ã«æ®ã£ãŠããã°ã2ã€ã®å¥ã©ãã«ãåäžã¹ã¯ãªãããåŒã¶äºéèµ·åç¶æ
ãäœãããŸããcrontabã«ã¯å¿
ã絶察ãã¹ã§æžããŠãã ããã
PROPOSEDãã¯ãªã¢ããªããŸãŸapplyãããšå€ãplistãæ··å
¥ãã
87è¡ç®ã®for f in "$PROPOSED"/*.plistã¯PROPOSEDã®å
šãã¡ã€ã«ãç¡å·®å¥ã«ã³ããŒããŸããååã®dryã§çæãããä»ååé€ããcronè¡ã®plistãæ®ã£ãŠãããšãåé€ããã¯ãã®ãžã§ããapplyã§åŸ©æŽ»ããŸããdryãå®è¡ããåã«rm -f ~/.claude/scripts/launchd-proposed/*.plistã§ã¯ãªã¢ããã®ãç¿æ
£ã«ããŸãã
çæplistã«EnvironmentVariablesããªãâânvmã»pyenvã»Homebrewã®ã³ãã³ããæ»ã¬
çæãã³ãã¬ãŒãïŒ54ã76è¡ç®ïŒã«EnvironmentVariablesããŒã¯å«ãŸããŠããŸãããlaunchdã¯ãŠãŒã¶ãŒã®.zshrcãèªãŸãªããããnvmã§ç®¡çããnodeãåŒã¶ã¹ã¯ãªããã¯èµ·åçŽåŸã«node: command not foundã§èœã¡ãŸããã¿ãŒããã«ããæåå®è¡ãããšåé¡ãªãåãã®ã«ãlaunchdçµç±ã§ã¯æ»ã¬ââãã®é察称ã蚺æãé£ããããŸããcom.shun.daily-brief.plistã®6ã9è¡ç®ã®PATHæååãææ¬ã«ãnodeãpythonã䜿ãå
šplistã«è¿œèšããŠãã ããã
LowPriorityIOãšNiceãçæplistã«ãªãââèªååããã©ã¢ã°ã©ãŠã³ãã劚害ãã
com.shun.daily-brief.plistã®12ã15è¡ç®ã«ã¯LowPriorityIOãšNice 10ããããŸãããçæãã³ãã¬ãŒãã«ã¯ãããŸãããèšå®ããªããã°ããã¯ã°ã©ãŠã³ããžã§ããéåžžã®I/Oåªå
床ã§èµ°ããŸãããã¡ã€ã«ã®å€§éèªã¿æžãã䌎ããžã§ãããšãã£ã¿ããã©ãŠã¶ã®å¿çãé
ãããçµéšãããå Žåããã®ããŒã®æç¡ã確èªããŠãã ããã
cronè¡ã®åé€å¿ãã¯æéç匟ââæ¬¡ã®OSã¢ããããŒãã§äºéèµ·åãã
ã¹ã¯ãªããã®applyåŸïŒ94ã95è¡ç®ïŒã«ãcronè¡ã¯æåã§åé€ããŠãã ããããšèŠåãåºãã ãã§ãåé€ã¯èªååãããŠããŸãããSequoiaã§ã¯cron daemonãèµ·åããªããããæ®ããŠãå®å šããšæããã¡ã§ãããmacOSã®ãã€ããŒã¢ããããŒãã§cron daemonã埩掻ããã±ãŒã¹ãå®éã«ãããŸããnoteèªåæçš¿ã1æ¥4åèµ°ã£ãŠAPIã®ã¬ãŒãå¶éã«åŒã£ããã£ãã®ã¯ãã®å€±æããã§ããç§»è¡å®äºã®å€å®ã«ãcronæ®éªžããŒãããå«ããããšã§åçºãé²ãã§ããŸãã
ãã¹ããã©ã¯ãã£ã¹
97è¡ã®ã¹ã¯ãªãããšå幎ã®éçšããèžçãããç§»è¡äœæ¥ã»æ¥åžžéçšã®äž¡æ¹ã§äœ¿ããã«ãŒã«éã§ãã
1. crontabã®ãšã³ããªã¯çµ¶å¯Ÿãã¹ã§æžã
~/.claude/scripts/xxx.shã§ã¯ãªãã/home/.../.claude/scripts/xxx.shãšæžããŸãã35è¡ç®ã®æ£èŠè¡šçŸã«ãããããã©ãã«ãæå³ããcom.shun.xxxã«ãªããŸããéå»ã®cronè¡ãæžãçŽãæéã¯ããããŸãããç§»è¡åŸã®ã©ãã«è¡çªã»äºéèµ·åã»ãã©ãŒã«ããã¯åœåã®æ··ä¹±ã3ã€åæã«é²ããŸãã
2. */N圢åŒã¯ç§»è¡åã«æå倿ãã
*/15 * * * *ïŒ15åããïŒã¯æ¬¡ã®ã©ã¡ããã«å€æããŸããééãåºå®ãªãStartIntervalïŒç§æå®ïŒãæãã·ã³ãã«ã§ãã
<key>StartInterval</key>
<integer>900</integer> <!-- 900ç§ = 15å -->
ç¹å®åæ°ã§ã®å®è¡ãå¿
èŠãªå Žåã¯åºå®å€ãé
åã§åæããŸãïŒ0ã»15ã»30ã»45åïŒã倿æŒãã¯plutil -lintã§æ€èšŒã§ããŸãã
3. dryã®åã«PROPOSEDãã£ã¬ã¯ããªãã¯ãªã¢ãã
rm -f ~/.claude/scripts/launchd-proposed/*.plist
~/.claude/scripts/cron-to-launchd.sh dry
ãã®2è¡ãã»ããã§å®è¡ããããšã§ãå€ãäžä»£ã®plistãapplyã«æ··å
¥ããåé¡ãé²ããŸãã
4. dryåŸã«plutil -lintã§å
šplistãæ€èšŒããŠããapplyãã
for f in ~/.claude/scripts/launchd-proposed/*.plist; do
echo "--- $(basename $f)"
plutil -lint "$f"
done
*/Næ··å
¥ã»XMLãšã¹ã±ãŒãæŒãã»æ§æãšã©ãŒãAppleå
¬åŒããŒã«ã§äºåã«åŒŸããŸããplutil -lintãOKãè¿ããªãplistã¯applyããŸããã
5. applyçŽåŸã«2ã³ãã³ãã§å®äºã確èªãã
# launchdåŽã®çšŒå確èª
launchctl list | grep com.shun
# cronæ®éªžç¢ºèªïŒ0ãªãOKïŒ
crontab -l 2>/dev/null | grep -vE '^\s*#' | grep -v '^$' | wc -l
2è¡ç®ã0ãè¿ããŠlaunchdåŽã®ãšã³ããªæ°ãç§»è¡å¯Ÿè±¡è¡æ°ãšäžèŽããŠããã°ãç§»è¡å®äºãšå€æã§ããŸãã
6. nodeã䜿ããžã§ãã®plistã«EnvironmentVariablesãæå远èšãã
çæçŽåŸã«<key>ProgramArguments</key>ã®çŽåãžæ¿å
¥ããŸãïŒ
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>~/.nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:~/.local/bin</string>
</dict>
nvmã®ããŒãžã§ã³çªå·ã¯å®ç°å¢ã«åãããŸããcom.shun.daily-brief.plistã®6ã9è¡ç®ãã¢ãã«ã§ãã
7. è€æ°æå»ã®plistã¯StartCalendarIntervalãé
åã«æžãçŽã
åäžã¹ã¯ãªããã2æå»ã§åãããªãã1ã€ã®plistã«é åã䜿ããŸãïŒcrontabã«2è¡æžããŠã©ãã«è¡çªãããªãïŒã
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>10</integer><key>Minute</key><integer>30</integer></dict>
</array>
com.shun.daily-brief.plistã®33ã47è¡ç®ã®èšè¿°ãå®äŸã§ãã
8. ããã¯ã°ã©ãŠã³ããžã§ãå
šè¬ã«LowPriorityIOãšNice 10ãèšå®ãã
äœæ¥ã®éªéãããªãããã«ãçæplistãã¹ãŠã«è¿œèšããŸãïŒ
<key>LowPriorityIO</key><true/>
<key>Nice</key><integer>10</integer>
èªååç°å¢ããååšæãæ¶ããããšã§ã人éã®äœæ¥é åã䟵é£ããªãèšèšã«ã§ããŸãã
9. ã³ãã³ãã«&ã»<ã»>ãå«ãŸããè¡ã¯æåã§plistãæžã
çæã«é ŒãããXMLãšã¹ã±ãŒããæ£ç¢ºã«è¡ããŸãïŒ
-
&â& -
<â< -
>â>
ãªãã€ã¬ã¯ãã¯åŒã³åºãã·ã§ã«ã¹ã¯ãªããã®å éšã«ç§»ãã®ãæãã¯ãªãŒã³ã§ããplistã®XMLå ã§ãªãã€ã¬ã¯ããæ±ãããšãããšãã»ãŒå¿ ããã®ãšã¹ã±ãŒãåé¡ã«åœãããŸãã
10. launchctl list com.shun.xxxã§LastExitStatusãå®æç¢ºèªãã
launchctl list com.shun.daily-brief
"LastExitStatus" = 0ãæ£åžžã0以å€ã®å Žåã¯ãã°ã確èªããŸãã鱿¬¡ã§ã®äžæ¬ç¢ºèªïŒ
launchctl list | grep com.shun | awk '{print $3}' | \
xargs -I{} sh -c 'launchctl list "{}" 2>/dev/null' | \
grep -E '"Label"|"LastExitStatus"'
11. launchctl kickstartã§ãªã³ããã³ãå®è¡ããŠãããã°ãã
ã¹ã±ãžã¥ãŒã«æå»ãåŸ ãã峿å®è¡ãããå ŽåïŒ
launchctl kickstart -k gui/$(id -u)/com.shun.xxx
-kã¯å®è¡äžã®ã€ã³ã¹ã¿ã³ã¹ãkillããŠåèµ·åããåªçãªãªãã·ã§ã³ã§ãããã°ã«äœãåºãªããã°PATHãã¹ã¯ãªããã®ãã¹åé¡ã§ãã
12. macOSã¢ããããŒãåŸã¯å šãµãŒãã¹ã®çæ»ã確èªãã
ãã€ããŒã¢ããããŒãã§launchdã®åäœãå€ããããšããããŸããã¢ããããŒãç¿æã®ãã€ãªãŒããªãŒããæ¥ãªããã°ããŸãlaunchctl list | grep com.shunãå©ããŸãããµãŒãã¹ãæ¶ããŠããã°åapplyã§åŸ©æŽ»ããŸãã
13. ç§»è¡å®äºã®ãå®äºæ¡ä»¶ãã3ã€å®çŸ©ããŠãã
ç§»è¡äœæ¥ã®ãçµããããææ§ã ãšç¢ºèªãçç¥ããã¡ã§ãã以äžãå®äºæ¡ä»¶ãšããŠèšå®ããŠããŸãïŒ
-
launchctl list | grep com.shunã®ãšã³ããªæ°ãç§»è¡å¯Ÿè±¡ã®cronè¡æ°ãšäžèŽãã -
crontab -l 2>/dev/null | grep -vE '^\s*#' | grep -v '^$' | wc -lã0ãè¿ã - å
šãµãŒãã¹ã®
LastExitStatusãå°ãªããšãååå®è¡åŸã«0ã«ãªã£ãŠãã
ãã®3ã€ãæºãããŠåããŠãç§»è¡å®äºããšèšããŸãã
14. ç§»è¡äœæ¥å šäœã®æèŠæéãäºåã«èŠç©ãã
cronè¡ã®æ¬æ°ã»*/N圢åŒã®å«ææ°ã»nvmäŸåãžã§ãã®æ°ãæ°ããŠããäœæ¥ãå§ããŸãã10æ¬ä»¥äžã§ããã°dryâplutilæ€èšŒâæåä¿®æ£âapplyâå®äºç¢ºèªã®äžé£ã90å以å
ã«çµãããŸãã30æ¬ä»¥äžããå Žåã¯4æ¡ä»¶ãæºããè¡ãå
ã«èªåç§»è¡ããæ®ããç¿æ¥ä»¥éã«æåç§»è¡ããå岿Šç¥ãçŸå®çã§ãã
ãŸãšã
macOSã®cron daemonãéãã«æ¢ãŸãåé¡ã¯ããšã©ãŒãåºãªãã¶ãçºèŠãé
ããŸããcrontab -lã«ãšã³ããªã䞊ãã§ããã®ã«8æã®ãã€ãªãŒããªãŒããæ¥ãªãã11æã®SNSæçš¿ãæµããªãââãã®éåæã«æ°ã¥ããŸã§æ°æéããããlaunchdãžã®ç§»è¡ã¯ãcronãå£ããŠãã察åŠãããã®ã§ã¯ãªãããmacOSã®ãã€ãã£ããªä»çµã¿ã«ä¹ãçŽãå
è¡æè³ããšããŠæããæ¹ãæ£ç¢ºã§ãã
97è¡ã®ã¹ã¯ãªããcron-to-launchd.shãããããšã¯åçŽã§ããcrontabã1è¡ãã€èªã¿ã5ãã£ãŒã«ããXMLã«å€æããplistãšããŠæžãåºããdryâplutilæ€èšŒâapplyã®3ã¹ãããã§ãåºå®ã¹ã±ãžã¥ãŒã«ã»çµ¶å¯Ÿãã¹ã»PATHäžèŠãªè¡ã®é圢ãéç£ã§ããŸãããã ããå
šèªåã®éæ³ãã§ã¯ãããŸããã*/N圢åŒã»~ãã¹ã»nvm/pyenväŸåã»è€æ°æå»ã»XMLãšã¹ã±ãŒã察象æåââãããã¯æåã§ååŠçãŸãã¯åŸåŠçãå¿
èŠã§ããã¹ã¯ãªãããã9å²ã®é圢ãäœããããšã§ãæåäœæ¥ã®å¯Ÿè±¡ãæç¢ºã«ãªãããã®æ§é ãçè§£ããŠäœ¿ãããšããç§»è¡åŸã«è©°ãŸããªãããã®äžçªã®è¿éã§ãã
launchdã«ç§»ãçµãããžã§ãã¯ãlaunchctl list com.shun.xxxã§ç¶æ
ãå³åº§ã«ç¢ºèªã§ããŸããLastExitStatusã0ã§ããããšãããåããŠããã¯ããã§ã¯ãªããåããŠãããã蚌æããŸããèªåŸç°å¢ã®ä¿¡é Œæ§ã¯ããåããŠãããšæã£ãŠãããæ¢ãŸã£ãŠããããšããçºèŠãæé€ããããšã§ç©ã¿äžãã£ãŠãããŸãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã
ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- å¶äœç©ã»èšäºã¯ bokuwalily.com ã«ãŸãšããŠããŸãð¥ïž
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
- AIå°å ¥ã»èªååã®çžè«ãšå®è£ ãã³ãã¬7æ¬ã®é åžã¯ å ¬åŒLINE ããð¬
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ