æå120äžã®èªååç°å¢ããSNSæçš¿ã¬ãŒã³ã ã14æ¥éå®å šã«æ»ãã§ããŸããã
ãããã·ã¹ãã ã¯æ¯æãhealthyããšå ±åãç¶ããŠããŸããããšã©ãŒã¯äžååºãŠããªããã¹ã¯ãªããã®exit codeã¯0ãwatchdogã¯å šéšã°ãªãŒã³ããªã®ã«Instagramã¯7æ29æ¥ãããTikTokã¯7æ28æ¥ãããäžæ¬ãæçš¿ãåºãŠããªãã£ãã
æ°ã¥ããã®ã¯å¥ã®çšäºã§ãã°ãæã£ããšãã§ããã
ãªããã®ä»çµã¿ãå¹ãã®ã
èªååã«æ £ããŠãããšãããããé害ã«ç¹æã®äžæ°å³ããããã£ãŠããŸããæŽŸæã«èœã¡ãã·ã¹ãã ã¯ããçŽããŸããåé¡ã¯éãã«æ»ãã§ããã·ã¹ãã ã§ãã
ä»åã®æ ¹æ¬åå ã¯ãç£èŠã®ãæ§é çæ»è§ãã§ããã
æ¢åã® content-watchdog.sh ã¯ãèšäºã¬ãŒã³ïŒnote / maker / series / amebaïŒã ããç£èŠå¯Ÿè±¡ãšããŠããŸãããSNSæçš¿ã¬ãŒã³ïŒX / Instagram / TikTok / ThreadsïŒã¯ãèšèšäžã©ãã«ãå«ãŸããŠããªãã£ããã³ãŒãã®ã³ã¡ã³ãã«ã¯æ£çŽã«æžããŠãããŸãã
# content-watchdog.sh 㯠article/note/maker/series/ameba ã ããèŠãŠããã
# X/IG/TikTok/Threads ã®æçš¿ã»è¿ä¿¡ã¯èª°ãç£èŠããŠããªãã£ãããã®çµæ
# IGæçš¿ã¯7/29ãããTikTokæçš¿ã¯7/28ããæ¢ãŸã£ããŸãŸ2é±éæ°ã¥ãããªãã£ãã
ããã¯ã³ãŒãã®ãã°ã§ã¯ãããŸãããå®è£ ããæç¹ã§ã¯SNSæçš¿ã¬ãŒã³ãwatchdogã«å ¥ããããšãæ³å®ããŠããªãã£ãââã€ãŸãèšèšã®æ»è§ã§ããç£èŠããŠããã€ããããç£èŠã®ç¶²ã«ããããå ¥ã£ãŠããªãã£ãã
exit codeãä¿¡çšããªãçç±
ããäžã€ã®èœãšã穎ã¯ãexit codeã«ããå¥å šæ§å€æã§ãã
SNSæçš¿ã®èªååã¹ã¯ãªããã¯ãæçš¿ãæåããŠã倱æããŠã exit 0 ã§çµããããšããããŸããããšãã°Instagramã®rate-limitã«åŒã£ããã£ãå Žåãã¹ã¯ãªããã¯ã仿¥ã®ã¯ã©ãŒã¿ã䜿ãåã£ãããšå€æããŠæ£åžžçµäºããŸããTikTokã®ãšãã¥ã¬ãŒã¿ãããã§ã«æçš¿æžã¿ããšè¿ããå Žåã exit 2 ãšããŠæ£åžžæ±ãããŸãã
ã€ãŸããããã»ã¹ãæ»ãã§ããªãããšãïŒãæçš¿ãåºãŠããããšãã§ã¯ãªãã
å®éã« sns-output-watchdog.sh ã®ã³ãŒããèŠããšããã®æèšãèšèšã«çŽçµããŠããŸãã
# ðŽ åŸæ¥ã¯ 'çµäº (' ã®åºçŸåæ°ã ããæ°ããŠãããããcircuit-break ã§ã need-login ã§ã
# ãå¥å
šããšå€å®ããŠããã2026-08-08 宿ž¬ã§ ig-1 ã¯12runäž8åãtt-1/tt-2 ã¯6å²ã
# ããã0ä»¶ã®ãŸãŸ exit 0 ã§çµãã£ãŠããã2é±éèª°ãæ°ã¥ããªãã£ãã
12åèµ°ã£ãŠ8åã¯0ä»¶ã§çµãã£ãŠãããããã§ãå
šéš exit 0 ãªã®ã§ãåŸæ¥ã®ç£èŠã§ã¯ã12åå®èµ°ïŒå¥å
šããšå€å®ããŠããŸããã
ææç©ã®æç¡ã§å€å®ãã
æ°ããç£èŠã®çºæ³ã¯åçŽã§ãããããã»ã¹ãå®èµ°ããããã§ã¯ãªãããåœæ¥ã®ãã°ã«æåããŒã«ãŒãååšããããã§çæ»ãå€å®ããã
æçš¿ã¹ã¯ãªãããæåãããšãããã°ã«ã¯ç¹å®ã®æååãæ®ããŸããããšãã°IGæçš¿ãªã OK postedãXèªåãã€ãŒããªã æçš¿æåãTikTokãªã run end (exit 0) ãŸã㯠run end (exit 2)ããããã®ããŒã«ãŒãåœæ¥ã®æ¥ä»ãšå
±ã«ãã°ã«ååšãããã©ãããæ°ããââããã ãã§ãã
ãã®çºæ³ã¯ã坿¥èªååãå幎以äžéçšããŠããäœéšããæ¥ãŠããŸãããã¹ã¯ãªãããåããŠãããããããã¢ãŠãããããåºãŠããããã®æ¹ããããžãã¹çãªæå³ã§ã®å¥å šæ§ã«çŽçµããŠããŸããæåã«åœ±é¿ããã®ã¯ã³ãŒããèµ°ãããšã§ã¯ãªããæçš¿ãåºãããšã ããã§ãã
ãå€å®äžèœããšãç°åžžããåºå¥ãã
ç£èŠèšèšã§ããäžã€éèŠãªã®ã¯ãã0ä»¶ã ããç°åžžããšãããããå€å®ã§ããªããã®åºå¥ã§ãã
ããšãã°ãããæ¥ã«TikTokã¹ã¯ãªãããäžåºŠãèµ·åããªãã£ãå Žåããã°ã«åœæ¥ã®æ¥ä»è¡ã¯ååšããŸããããã®ç¶æ ãã0ä»¶ïŒç°åžžããšå€å®ãããšãã€ã³ã¿ãŒãããæ¥ç¶ãäžæåããæ¥ããlaunchdãèµ·åã¿ã€ãã³ã°ãã¹ãããããæ¥ã«æ¯åã¢ã©ãŒããé£ã³ãŸããã¢ã©ãŒããæ¯æ¥é£ã¶ããã«ãªããšã人éã¯ã¢ã©ãŒããä¿¡çšããªããªããŸãââããã¯ç£èŠãæ©èœããªããªãå žåçãªãã¿ãŒã³ã§ãã
ã³ãŒãã§ã¯ has_today() 颿°ããã®åºå¥ãæ
ããŸãã
has_today() {
local file="$1" daymark="$2"
[ -f "$file" ] || return 1
/usr/bin/grep -qF "$daymark" "$file"
}
ãã°ã«åœæ¥è¡ããããããªããã° UNKNOWNïŒå€å®äžèœïŒãšããŠåé¡ããUNHEALTHYïŒç°åžžïŒãšã¯å¥ã®ã¢ã©ãŒãã§äžããŸãããå£ããŠãããã®ããç£èŠãå¹ããŠããªããã®ããæ··ãããšãäž¡æ¹ã®ä¿¡é Œæ§ãäžãããŸãã
å šäœã®æµã
ã·ã¹ãã ã®å šäœåã¯ãããªã£ãŠããŸãã
launchdïŒæ¯æ¥23:30 JSTïŒ
ââ sns-output-watchdog.sh
â
ââ æçš¿ã¬ãŒã³ã®ç¢ºèª
â ââ Xèªåãã€ãŒã autopost.log ããŒã«ãŒ: "æçš¿æå"
â ââ Xèªåãªãã©ã€ autoreply.log ããŒã«ãŒ: "å®äº: è¿ä¿¡"
â ââ X outbound outbound.log ããŒã«ãŒ: "å®äº"
â ââ Xããã x-1.log ããŒã«ãŒ: "çµäº ("
â ââ IGã«ã«ãŒã»ã«æçš¿ sns-ig-autopost.err.log ããŒã«ãŒ: "OK posted"
â ââ Threadsèªåæçš¿ persona-autopost-th-1.log ããŒã«ãŒ: "æçš¿æå"
â ââ TikTokæçš¿ tiktok-bokuwalily.log ããŒã«ãŒ: "run end (exit 0/2)"
â
ââ ææããŒã¹ã®ãã§ãã¯ïŒãããæ°ïŒ
â ââ x-1 / ig-1ã3 / th-1ã2 / tt-1ã3 ã®åã¬ãŒã³
â ââ åœæ¥ã®ãããåèšãéèš
â ââ 0ä»¶ or ææãŒãrunâ¥3å â UNHEALTHY
â
ââ çµæã Discord ã«éç¥
ââ ç°åžžãã â ðš UNHEALTHY
ââ å€å®äžèœ â â ïž UNKNOWN
ââ å
šæ£åžž â healthyïŒç¡éç¥ïŒ
launchdã®èšå®ã¯ com.lily.sns-output-watchdog.plist ã§ç®¡çããŠããŸããStartCalendarInterval ã§æ¯æ¥23:30 JSTã«èµ·åããRunAtLoad: false ã«ããŠããã®ã¯ãplistãããŒãããç¬éã«èª€çºç«ãããªãããã§ãã
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>23</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
</array>
<key>RunAtLoad</key>
<false/>
LowPriorityIO ãš Nice 10 ãèšå®ããŠããã®ã¯ãæ·±å€åž¯ã«ãã£ã¹ã¯I/Oã倧éã«æ¶è²»ãããããåŠçãšç«¶åããªãããã«ããããã§ãã
ã¿ã€ã ã¹ã¿ã³ãã®çœ
å®è£ ã®äžã§æãèŠèœãšããããèœãšã穎ãããã°ã®ã¿ã€ã ã¹ã¿ã³ã圢åŒã®æ··åšã§ãã
ä»åã®ç°å¢ã§ã¯ããã°ã®æ¥ä»è¡šèšã3çš®é¡æ··åšããŠããŸãã
TODAY_JST="$(date '+%Y-%m-%d')"
# â "2026-08-14" IGãªãŒããã¹ãã®ãã°åœ¢åŒ
TODAY_UTC="$(date -u '+%Y-%m-%d')"
# â "2026-08-13" social-autolikeã®ãã°åœ¢åŒïŒUTCèšé²ïŒ
TODAY_HUMAN="$(date '+%a %b %e')"
# â "Fri Aug 14" TikTokãã°ïŒlaunchdã®dateåºå圢åŒïŒ
JSTãšUTCã§æå€§1æ¥ãããŸããæ·±å€0ã9æã®éã¯JSTã§ã8æ14æ¥ãã§ããUTCã§ã¯ã8æ13æ¥ãã§ãããã®æéã«ç£èŠã¹ã¯ãªãããèµ°ããšãUTC圢åŒã®ãã°ã«å¯Ÿã㊠TODAY_JST ã§æ€çŽ¢ãããšãåœæ¥è¡ãªã â UNKNOWNããšèª€å€å®ããŸãã
TikTokã®ã±ãŒã¹ã¯ããã«ç¹æ®ã§ãã%e ã¯æ¥ä»ã空çœè©°ãã§åºåããŸãã8æ¥ã¯ Aug 8ïŒã¹ããŒã¹2ã€ïŒã«ãªããŸãã%d ã ãš 08 ã§åºåãããããããã°ãšäžèŽããŸããã
# TikTok: exit 0 ãš exit 2ïŒæçš¿æžã¿æ±ãïŒãæåãšã¿ãªãã
tt_ok=$(count_today "$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN" 'run end (exit 0)')
tt_maybe=$(count_today "$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN" 'run end (exit 2)')
check "tt-autopost" "$((tt_ok + tt_maybe))" 1 "TikTokæçš¿(exit0=$tt_ok exit2=$tt_maybe)" \
"$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN"
ãåœæ¥ã®ãã°ãèªãããšããäžèŠåçŽãªæäœããå®ã¯ã¿ã€ã ãŸãŒã³ãšãã©ãŒããããšãã2軞ã®åé¡ãå å ããŠããŸãã
ææãŒãæ€åºã®ä»çµã¿
æçš¿ã¬ãŒã³ïŒX/IG/TikTok/ThreadsïŒãšã¯å¥ã«ããããã»ãã©ããŒã¬ãŒã³ãç£èŠããŠããŸãããã¡ãã¯ãrunãå®èµ°ããããã§ã¯ãªããå®éã«ããããäœä»¶ã§ããããã§å€å®ããŸãã
sum_likes_today() {
local file="$1" day="$2"
[ -f "$file" ] || { echo ""; return; }
/usr/bin/grep -a "^$day" "$file" 2>/dev/null \
| /usr/bin/grep -oE 'ããã:[0-9]+' \
| /usr/bin/grep -oE '[0-9]+' \
| /usr/bin/awk '{s+=$1} END {print s+0}'
}
åœæ¥è¡ã ãã grep ã§çµã蟌ã¿ãããã:N 圢åŒã®æ°å€ãåç®ããŸããåèšã0ãªãããã®ã¬ãŒã³ã¯ãå®èµ°ããŠãããææãªãããšã㊠UNHEALTHY ã«åé¡ãããŸãã
count_dead_runs() {
local file="$1" day="$2"
/usr/bin/grep -a "^$day" "$file" 2>/dev/null \
| /usr/bin/grep -E 'çµäº \((need-login|circuit-break|error|rate-limit)\)' \
| /usr/bin/grep -c 'ããã:0' || true
}
need-loginïŒèªèšŒåãïŒã»circuit-breakïŒéè² è·åé¿ïŒã»errorã»rate-limit ãšããçµäºçç±ã〠ããã:0 ã®çµã¿åããã1æ¥ã«3å以äžããã°ãå¥å
šã«èŠããrunãå®ã¯æ©èœäžå
šãèµ·ãããŠããããšå€å®ããŸãã
宿ž¬ã§ ig-1 ã¬ãŒã³ã¯12 runäž8åããã®ãã¿ãŒã³ã§ãããexit codeã ãèŠãŠããåŸæ¥ã®ç£èŠã§ã¯ã12åå®èµ°ïŒå¥å šããšèª€å€å®ãç¶ããŠããââããã2é±éã®æ²é»ã«ã€ãªãããŸããã
count_today ã®èšèš
count_today() ã«ã¯åŸ®åŠãªèšèšå€æãå
¥ã£ãŠããŸãã
count_today() {
local file="$1" daymark="$2" marker="$3"
[ -f "$file" ] || { echo 0; return; }
/usr/bin/awk -v day="$daymark" -v mark="$marker" '
index($0, day) { seen = 1 }
seen && index($0, mark) { n++ }
END { print n + 0 }
' "$file"
}
grep ã§ã¯ãªã awk ã䜿ã£ãŠããã®ã¯çç±ããããŸãããã°ã¯è¿œèšåã§ãæšæ¥ä»¥åã®è¡ãæ®ã£ãŠããŸããgrep ã§åçŽã«ããŒã«ãŒãæ°ãããšãæšæ¥ä»¥åã®æåã仿¥ã®æåãšããŠã«ãŠã³ãããŠããŸããŸãã
awk ã®åŠçã¯ãåœæ¥ããŒã¯ãåããŠèŠãè¡ä»¥éã ããã«ãŠã³ã察象ã«ããããšããããžãã¯ã§ããseen ãã©ã°ãç«ã£ãåŸã«ããããŒã«ãŒãã«ãŠã³ãããªããããã«ããã忥以åã®æåå®çžŸã仿¥ã®æåãšããŠèª€ç®å®ããããšãé²ãã§ããŸãã
å®è£ ã®è©³çް
check() ãå€å®ãäžæ¬åãã
ååã§ç޹ä»ãã count_today() ã has_today() ã¯éšåã§ãããããæããŠãOK / UNKNOWN / UNHEALTHYãã«æ¯ãåããã®ã check() ã§ãã
check() {
local lane="$1" count="$2" min="$3" note="$4" file="${5:-}" daymark="${6:-}"
REPORT="$REPORT
$lane: $count ä»¶ (æäœ $min) $note"
if [ "$count" -ge "$min" ]; then
log "ok lane=$lane count=$count"
return
fi
if [ -n "$file" ] && ! has_today "$file" "$daymark"; then
UNKNOWN="${UNKNOWN:+$UNKNOWN,}$lane"
log "UNKNOWN lane=$lane åœæ¥è¡ãªã file=$file (æ¥ä»æªåºåããã®æ¥æªèµ·å)"
return
fi
FAILED="${FAILED:+$FAILED,}$lane"
log "UNHEALTHY lane=$lane count=$count min=$min"
}
åŒæ°ã¯6ã€ïŒã¬ãŒã³åã»ä»¶æ°ã»æäœä»¶æ°ã»æ³šèšã»ãã°ãã¹ã»åœæ¥ããŒã¯ã
å€å®ã®é åºãéèŠã§ãã ä»¶æ°ãè¶³ããŠããïŒcount -ge minïŒãªãå³ returnããã以å€ã®å Žåã ãããªã0ãªã®ããã調ã¹ãŸããããã°ã«åœæ¥è¡ããªãïŒhas_today ã falseïŒããªã UNKNOWNããã°ã¯ããã®ã«0ãªã UNHEALTHYããã®é åºãéã«ãããšããã°ãååšããªãããšãç°åžžãšèª€å€å®ããŠããŸããŸãã
UNKNOWN="${UNKNOWN:+$UNKNOWN,}$lane" ãšããæ§æãæå³çã§ãã${倿°:+å€} ã¯å€æ°ãé空ã®ãšãã ãå±éãããŸããFAILEDã ""ïŒç©ºïŒã®ãšã远å ãããš ,x-autopostïŒå
é ã«ã³ãïŒã«ãªã£ãŠããŸãã®ãé²ãã§ããŸãã
å®éã®åŒã³åºãã¯ãããªã£ãŠããŸãã
# Instagram: ã«ã«ãŒã»ã«èªåæçš¿ã
check "ig-autopost" \
"$(count_today "$CL_LOGS/sns-ig-autopost.err.log" "$TODAY_JST" 'OK posted')" \
1 "IGã«ã«ãŒã»ã«æçš¿" \
"$CL_LOGS/sns-ig-autopost.err.log" "$TODAY_JST"
5çªç®ãš6çªç®ã®åŒæ°ã¯ãã°ãã¹ãšåœæ¥ããŒã¯ã®ãã¢ã§ããcount_today() ã«æž¡ãããã®ãšåãå€ã check() ã«ãæž¡ããããã«ãã check() å
ã® has_today() ãåãããŒã¯æååã§ãåœæ¥è¡ãããããã確èªã§ããŸãã
set -uo pipefail ã«ããçç±
ã¹ã¯ãªããåé 㯠set -uo pipefail ã§ããset -e ã§ã¯ãããŸããã
set -uo pipefail
-uïŒæªå®çŸ©å€æ°ãåç
§ãããšãšã©ãŒã-o pipefailïŒãã€ãã©ã€ã³å
ã®ããããã®ã³ãã³ããé0ãè¿ããããã€ãã©ã€ã³å
šäœãå€±ææ±ãã-eïŒä»»æã®ã³ãã³ãã倱æã§ã¹ã¯ãªããçµäºïŒã¯å
¥ããŠããŸããã
çç±ã¯ grep ã®æåã«ãããŸããgrep -c ã¯ãããä»¶æ°ãã«ãŠã³ãããã³ãã³ãã§ããããã0ä»¶ã®ãšã exit 1 ãè¿ããŸããset -e ããããšãgrep -c ã0ä»¶ãããããç¬éã«ã¹ã¯ãªããå
šäœãæ»ã«ãŸãã
å®éã® count_dead_runs() ã®ãã€ãæ«å°ŸãèŠãŠãã ããã
count_dead_runs() {
local file="$1" day="$2"
[ -f "$file" ] || { echo ""; return; }
/usr/bin/grep -a "^$day" "$file" 2>/dev/null \
| /usr/bin/grep -E 'çµäº \((need-login|circuit-break|error|rate-limit)\)' \
| /usr/bin/grep -c 'ããã:0' || true
}
æ«å°Ÿã® || true ã grep -c ã® exit 1 ãåžåããŠããŸããpipefail ç°å¢ã§ãããã§åé¿ã§ããŸãããã || true ãæžãå¿ãããšãææãŒã runã1ä»¶ãååšããªãå¥å
šãªæ¥ã»ã©ã¹ã¯ãªãããéäžã§æ»ã¬ãšããé説çãªãã°ãçãŸããŸãã
æªå®çŸ©å€æ°ãžã®å¯ŸåŠã¯ ${5:-} ã ${dead:-0} ã§è¡ã£ãŠããŸããcheck() ã®åŒæ°5ã»6ã¯ãªãã·ã§ãã«ã§çç¥ã§ããããã« :- ã§ç©ºæåããã©ã«ããèšå®ããŠããŸãã
launchd 㯠bash ã® PATH ãç¥ããªã
plist ã® EnvironmentVariables ã»ã¯ã·ã§ã³ãèŠãŠãã ããã
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:~/.local/bin</string>
</dict>
launchd ããèµ·åãããããã»ã¹ã¯ãã°ã€ã³ã·ã§ã«ãçµç±ããŸããã.zshrc ã .bash_profile ãèªãŸããŸãããã€ãŸã nvm ã§ã€ã³ã¹ããŒã«ãã node ããhomebrew ã§ã€ã³ã¹ããŒã«ããããŒã«ããããã©ã«ãã§ã¯ PATH ã«å«ãŸããŸããã
ã ããã¹ã¯ãªããå ã§äœ¿ããã¹ãŠã®ã³ãã³ãã絶察ãã¹ã§æžããŠããŸãã
/usr/bin/grep -qF "$daymark" "$file"
/usr/bin/awk -v day="$daymark" -v mark="$marker" '...' "$file"
/usr/bin/python3 "$DISCORD" post alerts "$1"
grep ãçŽ ã®ãŸãŸ grep ãšæžããšãhomebrew çã® grep ã /opt/homebrew/bin/grep ã«ããç°å¢ã§ã¯æ£åžžåäœããã®ã« launchd çµç±ã§ã¯ãã³ãã³ããèŠã€ãããªããã§ãµã€ã¬ã³ãã«å€±æããŸããã¹ã¯ãªããã¯èµ°ã£ãŠããã®ã«äœãåºåãããªããšãããäžçªãããã°ãã«ããå£ãæ¹ãããŸãã
stdout ãš stderr ãåäžãã¡ã€ã«ã«éãã
<key>StandardOutPath</key>
<string>~/.claude/logs/sns-output-watchdog.launchd.log</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/sns-output-watchdog.launchd.log</string>
stdout ãš stderr ãåããã¡ã€ã«ã«åããŠããã®ã¯æå³çã§ãã
ã¹ã¯ãªããèªèº«ã¯ log() 颿°ã§ ~/.claude/logs/sns-output-watchdog.log ã«æžã蟌ã¿ãŸããlaunchd ã®åºåãã¡ã€ã«ã¯ãããšã¯å¥ã§ãlaunchd èªèº«ãèµ·åã«å€±æãããšãïŒãã¹äžæ£ã»æš©éãšã©ãŒã»ã·ã§ã«æ§æãšã©ãŒãªã©ïŒã®ãšã©ãŒã¡ãã»ãŒãžã stderr ã«åºãŸããããã stderr ã ãå¥ãã¡ã€ã«ã«ãããšãã©ã¡ããèŠãã°ããããã§è¿·ãããšã«ãªããŸããåäžãã¡ã€ã«ã«çµ±åããŠããã°ãäœããã£ããšãã«1ãã¡ã€ã«ã ãèŠãã°å
šè²ãããããŸãã
notify() ã¯å€±æã蚱容ãã
notify() {
[ -f "$DISCORD" ] || return 0
/usr/bin/python3 "$DISCORD" post alerts "$1" >/dev/null 2>&1 \
|| log "discordéç¥ã«å€±æ(ãããã¯ãŒã¯?)"
}
2ç¹ã®èšèšå€æãå ¥ã£ãŠããŸãã
ã²ãšã€ç®ïŒ $DISCORD ãã¡ã€ã«ãååšããªãå Žå㯠return 0 ã§é»ã£ãŠçµãããŸããwatchdog ã¹ã¯ãªãããå¥ã®ãã·ã³ã CI ç°å¢ã§ã䜿ããŸãããããšããDiscord ããŒã«ããªããŠãã¹ã¯ãªããèªäœã¯å£ããŸããã
ãµãã€ç®ïŒ éç¥ã«å€±æããŠãã¹ã¯ãªãããèœãšããŸããã|| log "..." ã§ãã°ã ãæ®ããŠç¶ç¶ããŸããwatchdog ããDiscord ãå¿çããªãããèªåãæ»ã¬ãã§ã¯æ¬æ«è»¢åã§ããæ·±å€ã«ãããã¯ãŒã¯ãäžæåããå ŽåãDiscord éç¥ã¯å±ããªããŠãç¿æãã°ãèŠãã°çµæãããããŸãã
ææããŒã¹ã®ãã§ãã¯ã§ã空ãã©ããããå ã«èŠã
for lane in x-1 ig-1 ig-2 ig-3 th-1 th-2 tt-1 tt-2 tt-3; do
lf="$SA_LOGS/$lane.log"
[ -f "$lf" ] || continue
likes=$(sum_likes_today "$lf" "$TODAY_UTC")
dead=$(count_dead_runs "$lf" "$TODAY_UTC")
if [ -n "$likes" ] && [ "$likes" = "0" ]; then
FAILED="${FAILED:+$FAILED,}$lane-likes0"
log "UNHEALTHY lane=$lane æ¬æ¥ã®ãããåèš=0 (ææãŒãrun=${dead:-?}å)"
elif [ -n "$dead" ] && [ "${dead:-0}" -ge 3 ]; then
FAILED="${FAILED:+$FAILED,}$lane-dead${dead}"
log "UNHEALTHY lane=$lane ææãŒãrunã${dead}å (ãããåèš=${likes})"
else
log "OK lane=$lane ãããåèš=${likes:-?} ææãŒãrun=${dead:-0}å"
fi
done
if [ -n "$likes" ] && [ "$likes" = "0" ] ãšããäºæ®µéãã§ãã¯ã«æ³šç®ããŠãã ããã
sum_likes_today() ã¯ãã°ãã¡ã€ã«ãååšããªãå Žåã«ç©ºæå "" ãè¿ããŸãã
sum_likes_today() {
local file="$1" day="$2"
[ -f "$file" ] || { echo ""; return; }
...
}
空æåã®ãŸãŸ [ "$likes" = "0" ] ãè©äŸ¡ãããšã空æå â 0ãã§åœãã€ãŸãããããä»¶æ°ã¯ãŒãã§ã¯ãªã â OKããšèª€å€å®ããŸãã-n "$likes" ãå
ã«è©äŸ¡ããããšã§ããããããã°ãååšããªã â ã¹ãããããšããã°ã¯ããã0ä»¶ â UNHEALTHYããåããŠããŸãã
ãããã现ããæ¡ä»¶ã®ç©ã¿éããã99%ã®æ¥ã¯æ£ããåãããç¹æ®ãªæ¥ã ãèª€å ±ãåºãããé²ããŸãã
ç§ãè©°ãŸã£ã話
èšèšã®ããããªéšåã ãèŠããã®ã¯æ£çŽãããªãã®ã§ãå®éã«è©°ãŸã£ã倱æãæžããŸããçç¶âåå âçŽãæ¹ã®é ã§ã
è©°ãŸãâ ïŒç¿æãwatchdog ãå®å šç¡é³ã«ãªã£ã
çç¶ã count_dead_runs() ã远å ããç¿æãDiscord ã«äœãæ¥ãªãããã°ãã¡ã€ã«ãéããš ===== sns-output-watchdog start ===== ã®è¡ã¯ãããã§ããã®æ°è¡åŸã§ãã°ãéåããŠãããresult=healthy ã result=unhealthy ãåºãŠããªãã
åå ã grep -c 'ããã:0' ãããã0ä»¶ã®ãšã exit 1 ãè¿ã仿§ã§ããããã€ãã©ã€ã³ã®æ«å°Ÿã«æžããŠããã®ã§ pipefail ããã€ãå
šäœãå€±ææ±ãã«ããŠã颿°ãç°åžžçµäºãåŒã³åºãå
ã®ã¹ã¯ãªãããããã§æ»ãã§ããã
æåã®ã³ãŒãã¯æ«å°Ÿã« || true ããªãã£ãïŒ
# å£ããŠããããŒãžã§ã³
| /usr/bin/grep -c 'ããã:0'
çŽãæ¹ã || true ã远å ããã ãã§ãã
| /usr/bin/grep -c 'ããã:0' || true
ãã ããããã°ã«30åããããŸãããçç¶ããå®å šç¡é³ããªã®ã§ãæåã¯ãlaunchd ãèµ·åããŠããªãããDiscord ããŒã«ãå£ããããšçã£ãŠããŸãããwatchdog ã¹ã¯ãªããèªèº«ãã¯ã©ãã·ã¥ããŠãããšã¯èããªãã£ãã
æèšã watchdog ãç¡é³ã®ãšãããŸã result= ãšããããŒã¯ãŒãããã°ã§æ€çŽ¢ããŸãããã®ããŒã«ãŒãæ«å°Ÿã«ããã°æ£åžžçµäºã§ãããªããã°ã¹ã¯ãªãããéäžã§æ»ãã§ããŸãã
tail -50 ~/.claude/logs/sns-output-watchdog.log | grep -E 'result=|start'
è©°ãŸãâ¡ïŒTikTokã¬ãŒã³ãæ¯æ¥ UNKNOWN ã«ãªã
çç¶ã å°å
¥ç¿æ¥ãã tt-autopost ãæ¯æ¥ UNKNOWN ã§è¿ã£ãŠãããå®éã® TikTok ã¢ã«ãŠã³ãã確èªãããšæçš¿ã¯åºãŠãããã¹ã¯ãªããã®ãã°ããšæã£ãŠã³ãŒããèªã¿è¿ããŠãåé¡ãèŠã€ãããªãã
åå ã %e ãš %d ã®æ··åã§ãã
æåã®å®è£
ã¯æ¥ä»ãã©ãŒãããã %d ã§æžããŠããŸããã
# æåã®ïŒå£ããŠããïŒå®è£
TODAY_HUMAN="$(date '+%a %b %d')"
# 8æ8æ¥ â "Fri Aug 08"
TikTok ã®ãã°ãã¡ã€ã«ã head -10 ã§ç¢ºèªãããšãlaunchd ãåºåããæ¥ä»ã¯ %e 圢åŒïŒæ¥ã空çœè©°ãïŒã§ããã
Fri Aug 8 06:15:22 JST 2026
"Fri Aug 8"ïŒã¹ããŒã¹2ã€ïŒãš "Fri Aug 08" ã¯æååãšããŠäžèŽããŸãããhas_today() ãåžžã« false ãè¿ãã®ã§ãæ¯æ¥ UNKNOWN ãšå€å®ãç¶ããŠããã
çŽãæ¹ã ãã©ãŒãããã %e ã«å€æŽã
TODAY_HUMAN="$(date '+%a %b %e')"
# 8æ8æ¥ â "Fri Aug 8"ïŒã¹ããŒã¹2ã€ïŒ
æèšã grep ã®æ€çŽ¢æååãæ±ºããåã«ãå¿
ãå®éã®ãã°ã®å
é è¡ã head -5 ã§ç¢ºèªãããæ³å®ãã©ãŒãããã§æžããšããã°åºåããã°ã©ã ã®ãã©ãŒããããšäžèŽããªãããšãæ®éã«ãããŸãã
è©°ãŸãâ¢ïŒplist ãããŒããããæŒéã«éç¥ãé£ãã
çç¶ã èšå®ã倿Žã㊠launchctl unload && launchctl load ãå®è¡ããã®ãæŒã®13æãçŽåŸã« Discord ã«éç¥ãå±ããã23:30 ã«èµ°ãã¯ããªã®ã«ãªãïŒ
åå ã RunAtLoad ã®èšå®ã true ã«ããŠããããã§ãã
<!-- 誀ã£ãèšå® -->
<key>RunAtLoad</key>
<true/>
launchd ã® RunAtLoad: true ã¯ããžã§ããããŒãããç¬éã«ã1åå®è¡ããããšããæå³ã§ããéçºäžã«ãããŒãããŠããåäœç¢ºèªãããããšèšå®ãããŸãŸããããã¯ã·ã§ã³ã® plist ã«æ®ã£ãŠããŸããã
æŒéã«å®è¡ããããšåœç¶ãã°ã«åœæ¥è¡ãå°ãªããè€æ°ã®ã¬ãŒã³ã UNKNOWN æ±ãã«ãªããŸããç£èŠå¯Ÿè±¡ã®ã·ã¹ãã ã¯æ£åžžãªã®ã«ãwatchdog ã誀ã£ã UNKNOWN éç¥ãåºãç¶ããã
çŽãæ¹ã RunAtLoad: false ã«å€æŽãæåã§ã®åäœç¢ºèªã¯ launchctl start ã³ãã³ãã§è¡ããŸãã
launchctl start com.lily.sns-output-watchdog
RunAtLoad ã®æç¡ã¯ãæ¬çª plist ãšããŠäœ¿ããšãã®ãã§ãã¯ãªã¹ãé
ç®ããšããŠæèããŠããå¿
èŠããããŸããã»ãŒå¿
ã false ã§ããã
è©°ãŸãâ£ïŒåæ¥ã®æåå®çžŸã仿¥ã®æåãšããŠã«ãŠã³ãããŠãã
çç¶ã count_today() ã®æåã®ããŒãžã§ã³ã¯ grep ã§æžããŠããŸããã
# å£ããŠããæåã®å®è£
count_today_broken() {
local file="$1" daymark="$2" marker="$3"
[ -f "$file" ] || { echo 0; return; }
/usr/bin/grep -c "$marker" "$file"
}
ãã®å®è£ ã§2é±ééçšãããšãããããæ¥IGã®ã«ã«ãŒã»ã«æçš¿ãå®éã«ã¯1æ¬ãåºãŠããªãã®ã« watchdog ããOKããšè¿ããã
åå ã ãã°ã¯è¿œèšåã§ãæšæ¥ã®æåãåæ¥ã®æåããã¹ãŠåããã¡ã€ã«ã«ç©ã¿éãªã£ãŠããŸããåçŽã« grep -c "$marker" ãããšããã¡ã€ã«å
šäœã§ããŒã«ãŒãäœååºçŸããããæ°ããŸããæšæ¥æçš¿ã§ããŠããã°ã仿¥æçš¿ã§ããŠããªããŠãã1ä»¶ä»¥äž â OKããšå€å®ããŸãã
çŽãæ¹ã awk ã«æžãæ¿ããŠããåœæ¥ããŒã¯ãåºçŸããè¡ä»¥éã ããã«ãŠã³ã察象ã«ãããããžãã¯ãå
¥ããŸããã
count_today() {
local file="$1" daymark="$2" marker="$3"
[ -f "$file" ] || { echo 0; return; }
/usr/bin/awk -v day="$daymark" -v mark="$marker" '
index($0, day) { seen = 1 }
seen && index($0, mark) { n++ }
END { print n + 0 }
' "$file"
}
seen ãã©ã°ãç«ã£ãåŸã«ããããŒã«ãŒãã«ãŠã³ãããªããåœæ¥è¡ïŒæ¥ä»æååãå«ãè¡ïŒãåºãŠããŠåã㊠seen = 1 ã«ãªããŸãã
æèšã ãç£èŠã¹ã¯ãªããã healthy ãšèšã£ãŠããããä¿¡çšããããªãããšãwatchdog èªèº«ãæ£ããã«ãŠã³ãããŠãããã©ããããå®éã®ãã°ãšçªãåãããŠç¢ºèªããã¿ã€ãã³ã°ãå¿ èŠã§ããç§ã¯å®è£ ãã2é±éåŸã«ãå¥ã®çšäºã§ãã°ãæã£ããšãã«åããŠæ°ã¥ããŸããã
è©°ãŸãâ€ïŒç£èŠããå¥å šããšèšãç¶ãã2é±éã®æ§é
ããã¯ä»åã®éå®³ã®æ ¹æ¬ã§ãåè¿°ã®åå¥ãã°ãšã¯å°ãæ§è³ªãéããŸãã
content-watchdog.sh ã¯æ¯æ¥ 23:30 ã«èµ°ãããã°ã« result=healthy ãæ®ããŠããŸãããDiscord ãžã®éç¥ããç°åžžãªããã®ãŸãŸãã ãããã·ã¹ãã ã¯æ£åžžãã ãšæã£ãŠããã
åé¡ã¯ç£èŠå¯Ÿè±¡ã®åãéãã§ããcontent-watchdog.sh ã¯èšäºã¬ãŒã³ïŒnote / maker / series / amebaïŒã®çå確èªãããã¹ã¯ãªããã§ãSNS æçš¿ã¬ãŒã³ã¯ã¹ã³ãŒãå€ã§ãããã³ãŒãã«ãæ£çŽã«æžããŠãããŸãã
# content-watchdog.sh 㯠article/note/maker/series/ameba ã ããèŠãŠããã
# X/IG/TikTok/Threads ã®æçš¿ã»è¿ä¿¡ã¯èª°ãç£èŠããŠããªãã£ãã
ãwatchdog ã healthy ãšèšã£ãŠããããšãç£èŠç¶²ãå šã¬ãŒã³ãã«ããŒããŠãããã¯å¥ã®åœé¡ã§ããã§ãæ¯æ¥ healthy ã®éç¥ãæ¥ãŠãããšã人éã¯ãã€ã®éã«ãæ··åããŸãã
çŽãæ¹ã¯2ã€ã
ã²ãšã€ã¯ sns-output-watchdog.sh ãæ°ãã«äœã£ãŠ SNS ã¬ãŒã³ãç£èŠå¯Ÿè±¡ã«å ããããšïŒä»åã®æ¬é¡ïŒã
ããã²ãšã€ã¯ Discord ã®éç¥ã¡ãã»ãŒãžã«ç£èŠå¯Ÿè±¡ã¬ãŒã³ã®äžèЧãåžžã«å«ããããã«ããããšã
REPORT="$REPORT
$lane: $count ä»¶ (æäœ $min) $note"
REPORT 倿°ã«ãã¹ãŠã®ã¬ãŒã³ã®ç¢ºèªçµæãç©ã¿éãªã£ãŠãããDiscord éç¥ã«å
šéšå«ãŸããŸããã仿¥ã¯ x-autopost / x-autoreply / x-outbound / x-like / ig-autopost / th-autopost / tt-autopost ã確èªããããšããæ
å ±ãéç¥ã«åºãŸããç£èŠã¹ã¯ãªãããäœãèŠãŠãäœãèŠãŠããªããã人éã«ããããŸãã
èªååã·ã¹ãã ã®å¥å šæ§å ±åã§å€§äºãªã®ã¯ãç°åžžãªãããšèšãããšã§ã¯ãªãããäœã確èªããŠãç°åžžãªããšå€æãããã瀺ãããšã ãšã2é±éã®æ²é»ããåŠã³ãŸããã
ã€ãŸãããã€ã³ã
ååãšäžæ®µã§ç޹ä»ãã5ã€ã®è©°ãŸãïŒwatchdogç¡é³ã»TikTok UNKNOWNã»RunAtLoad誀çºç«ã»åæ¥æåã®èª€ç®å®ã»æ§é çæ»è§ïŒä»¥å€ã«ããåãèšèšãå®è£ ã»éçšããŠããäžã§ãŸã èžã¿ãããèœãšã穎ããããŸããç®æ¡æžãã§ç¶²çŸ ããŸãã
- plist ã®
~ã¯launchdã§å±éãããªã
StandardOutPath ã« ~/ ã§æžããšãlaunchdã¯ããããªãã©ã«æååãšããŠè§£éããŠããŒã ãå±éããŸããããã°ãã¡ã€ã«ãçæããããls ~/.claude/logs/ ã確èªããŠãåœç¶ãã¡ã€ã«ã¯ååšããªãç¶æ
ã«ãªããŸããå®éã® com.lily.sns-output-watchdog.plist ã®æžãæ¹ãèŠãŠãã ããã
<key>StandardOutPath</key>
<string>~/.claude/logs/sns-output-watchdog.launchd.log</string>
<key>StandardErrorPath</key>
<string>~/.claude/logs/sns-output-watchdog.launchd.log</string>
ããŒã ã®ç¥èš ~ ã¯äžå䜿ã£ãŠããŸãããProgramArguments ã®ã¹ã¯ãªãããã¹ã EnvironmentVariables ã® PATH ããå
šéšãã«ãã¹ã§ããplist å
ã§ ~ ã䜿ã£ãŠããå Žæã¯ãªããšèŠããŠãã ããã
- 3çš®é¡ã®ã¿ã€ã ã¹ã¿ã³ã圢åŒã1ã¹ã¯ãªããå ã«å ±åãã
sns-output-watchdog.sh ã®å
é ãèªããšãTODAY_ 倿°ã3çš®é¡ãããŸãã
TODAY_JST="$(date '+%Y-%m-%d')" # â "2026-08-14" IGãªãŒããã¹ãç³»
TODAY_UTC="$(date -u '+%Y-%m-%d')" # â "2026-08-13" social-autolikeå
šè¬
TODAY_HUMAN="$(date '+%a %b %e')" # â "Fri Aug 14" TikTokãã°ïŒlaunchd圢åŒïŒ
ãã°ãç£èŠå¯Ÿè±¡ã«è¿œå ãããã³ã«ããã®ããŒã«ã¯ã©ã®åœ¢åŒã§ã¿ã€ã ã¹ã¿ã³ããåºåããããã head -5 ãã°ãã¡ã€ã« ã§å¿
ãç®èŠç¢ºèªããå¿
èŠããããŸããJSTåºå®ã®ã¹ã¯ãªããã«UTC圢åŒã®ãã°ãæž¡ããšãæ·±å€0ã9æã®éã¯1æ¥ãã㊠UNKNOWN ãç¶ããŸããæ°ããã¬ãŒã³ã远å ãããšãã«æãé«é »åºŠã§èžãèœãšã穎ã§ãã
bash -uç°å¢ã§ã¯å€æ°ã®åæåå¿ããã¯ã©ãã·ã¥ãåŒãèµ·ãã
set -uo pipefail ã® -u ã¯ãæªå®çŸ©å€æ°ãåç
§ãããšãšã©ãŒãã§ããã¹ã¯ãªããåé ã®3è¡ã
FAILED=""
UNKNOWN=""
REPORT=""
ãããæžãå¿ãããšãæåã® check() ãåŒã°ããæç¹ã§ ${UNKNOWN:+$UNKNOWN,}$lane ã®å±éããšã©ãŒã«ãªããŸããçç¶ã¯ãŸãããwatchdog ãå®å
šç¡é³ãã§ããgrep -c || true ã®å¿ããšçç¶ãåããªã®ã§ããããã°æã«æ··ä¹±ããŸãã倿°ã®ç©ºå®£èšãæžããåŸ bash -n ã¹ã¯ãªããå ã§æ§æãã§ãã¯ãéããŠãã plist ãããŒãããã®ã確å®ã§ãã
- TikTok ã®
exit 2ãå€±ææ±ãã«ããŠããŸã
TikTokã®ãšãã¥ã¬ãŒã¿çµç±æçš¿ã¹ã¯ãªããã¯ããã§ã«æ¬æ¥åãæçš¿æžã¿ããšå€æããå Žåã« exit 2 ã§çµãããŸããããã¯æ£åžžç³»ã§ããã¹ã¯ãªããã§ã¯ run end (exit 0) ãš run end (exit 2) ã®äž¡æ¹ãæåãšããŠã«ãŠã³ãããŠããŸãã
tt_ok=$(count_today "$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN" 'run end (exit 0)')
tt_maybe=$(count_today "$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN" 'run end (exit 2)')
check "tt-autopost" "$((tt_ok + tt_maybe))" 1 "TikTokæçš¿(exit0=$tt_ok exit2=$tt_maybe)" \
"$CL_LOGS/tiktok-bokuwalily.log" "$TODAY_HUMAN"
exit 2 ãç¥ããã« exit 0 ã ããããŒã«ãŒã«ãããšãTikTokããæçš¿æžã¿ããšæ£ãã倿ããæ¥ã»ã© UNHEALTHY ãåºãŸããããŒã«ã® exit code ã®ã»ãã³ãã£ã¯ã¹ã¯ãã°ãèªãã§ç¢ºèªããå¿
èŠããããŸãã
- ãã°ãã£ã¬ã¯ããªã2ç®æãã
ã¹ã¯ãªããã«ã¯ SA_LOGSïŒsocial-autolikeãããžã§ã¯ãã®logs/ïŒãš CL_LOGSïŒ~/.claude/logs/ïŒã®2ã€ã®ããŒã¹ãã£ã¬ã¯ããªããããŸããXã»Threadsã»TikTokã®ãããã¬ãŒã³ã¯ SA_LOGS äžãIGæçš¿ã»TikTokæçš¿ã¯ CL_LOGS äžã«æžãããŸããæ°ããã¬ãŒã³ã远å ãããšããã©ã¡ãã®ãã£ã¬ã¯ããªã䜿ã£ãŠããããããŒã«ã®ã³ãŒãã§ç¢ºèªããªããšããã¹ãåããã«æ¯æ¥ UNKNOWN ã«ãªããŸãã
REPORT倿°ãè¥å€§åããŠDiscordã®æåå¶éã«åŒã£ããã
check() ãåŒã°ãããã³ã« REPORT ã«1è¡ãã€è¿œèšããŸããx-autopostã»x-autoreplyã»x-outboundã»x-likeã»ig-autopostã»th-autopostã»tt-autopostã®7ã¬ãŒã³åã«å ãããããç³»9ã¬ãŒã³åã®çµæãå
¥ããŸããUNHEALTHYæã®éç¥ã« $REPORT ãå
šéšä»äžãããšãDiscordã®2000åå¶éãè¶
ããŠæ«å°ŸãåããŸããããªããã®ã¬ãŒã³ãèµ€ãã®ãããéç¥ããèªã¿åããªããšããæ¬æ«è»¢åãªç¶æ
ã«ãªãã®ã§ãREPORT ã¯èŠçŽåœ¢åŒãå¥ãã¡ã€ã«åç
§ã«ããæ€èšãå¿
èŠã§ãã
bashã®æ§æãšã©ãŒã¯watchdogç¬èªãã°ã«ã¯åºãªã
ã¹ã¯ãªããã«æ§æãšã©ãŒããããš bash ãèµ·åçŽåŸã«çµäºããŸããwatchdog ãå
éšã§ log() 颿°ãåŒã¶ããåã«æ»ãã§ããã®ã§ã~/.claude/logs/sns-output-watchdog.log ã«ã¯äœãæ®ããŸããããšã©ãŒã®åºåå
㯠launchd ã®ãã°ïŒ~/.claude/logs/sns-output-watchdog.launchd.logïŒã ãã§ããwatchdog ãç¡é³ã®ãšãã®èšºæé åºã¯ããŸã launchd ãã° â æ¬¡ã« watchdog ç¬èªãã°ãã§ãã
tail -20 ~/.claude/logs/sns-output-watchdog.launchd.log
syntax error near unexpected token ã command not found ãåºãŠããã°ãã¹ã¯ãªããæ¬äœã PATH èšå®ã®åé¡ã§ãã
- healthy ã®ãšãéç¥ãããªãã®ã§æ°ã¬ãŒã³ã®è¿œå ã確èªã§ããªã
watchdog ã¯ç°åžžããããšãã ã Discord ã«éç¥ããèšèšã§ããå
šã¬ãŒã³ãæ£åžžãªæ¥ã¯ãã°ã« result=healthy ãæ®ãã ãã§éç¥ã¯é£ã³ãŸãããæ°ããã¬ãŒã³ã check() ã«è¿œå ããç¿æ¥ããéç¥ãæ¥ãªã â healthyããšå®å¿ããããšããã§ãããããã®ã¬ãŒã³ã远å ã§ããŠããããã¯çŽæ¥ç¢ºèªã§ããŸããã
launchctl start com.lily.sns-output-watchdog
tail -f ~/.claude/logs/sns-output-watchdog.log
æåã§èµ·åããŠãªã¢ã«ã¿ã€ã ã«ãã°ãèŠãã®ãå¯äžã®ç¢ºèªææ®µã§ããok lane=æ°ã¬ãŒã³å count=N ã®è¡ãåºãŠããã°ç£èŠç¶²ã«å
¥ã£ãŠããŸãã
- ææãŒãrunã®éŸå€ã3åãã¯æ ¹æ ãæã£ãŠèšå®ãã
count_dead_runs() ã®çµæã 3 以äžã®ãšã UNHEALTHY ãšå€å®ããŠããŸãã1åã2åã§å³ã¢ã©ãŒãã«ããªãçç±ã¯ããããã¯ãŒã¯äžææãã»ãã·ã§ã³åèªèšŒãªã©ã§åçºã® need-login ãåºãããšã宿ž¬ã§é »ç¹ã«ããããã§ãã2026-08-08ã®å®æž¬ã§ã¯ ig-1 ã¯12åäž8åãææãŒãrunã§ããããã®æ°åãæ ¹æ ã«ã3å以äžãªãæ§é çãªåé¡ããšããéŸå€ãèšå®ããŠããŸããéŸå€ãäœããããããšæ¯æ¥ã¢ã©ãŒããé£ãã§ã¢ã©ãŒãç²ããèµ·ããç£èŠãæ©èœããªããªããŸããéŸå€ãé«ããããããšæ¬ç©ã®é害ãèŠéããŸããæåã¯3åããå§ããŠãå®ç°å¢ã§ã®åœéœæ§çãèŠãªãã調æŽããã®ãçŸå®çã§ãã
sum_likes_todayã®ç©ºæåãš"0"ã®åºå¥ãå¿ãããšèª€å€å®ãèµ·ãã
ãã°ãã¡ã€ã«ãååšããªãå Žåãsum_likes_today() 㯠echo "" ã§ç©ºæåãè¿ããŸãã空æåã®ãŸãŸ [ "$likes" = "0" ] ãè©äŸ¡ãããšã空æå â 0ãã§åœââã€ãŸãããŒãã§ã¯ãªã â OKããšãã誀å€å®ã«ãªããŸããããã°ãååšããªãããšããã°ã¯ããã0ä»¶ããåããããã« [ -n "$likes" ] && [ "$likes" = "0" ] ã®äºæ®µéãã§ãã¯ãå¿
èŠã§ãé åºãéã«ããŠãå£ããŸãããã®æ§é 㯠p2 ã§è©³ããè§ŠããŸããããã空æåãšãŒããæ··ãããšå¿
ã誀å€å®ãåºãããšããååãšããŠç£èŠã¹ã¯ãªããå
šè¬ã«é©çšã§ããŸãã
ãã¹ããã©ã¯ãã£ã¹
2é±éã®æ²é»ãšå®è£ ã§åŸãæèšããä»ã®èªååã·ã¹ãã ã«ããã®ãŸãŸäœ¿ããã«ãŒã«ã«æŽçããŸãã
1. ãããã»ã¹ãå®èµ°ããããã§ã¯ãªããææç©ãåœæ¥ãã°ã«ååšããããã§å€å®ãã
exit code ã¯æ£åžžã§ãæçš¿ã¯åºãŠããªãããã®å®äŸã蚌æããéããããã»ã¹ã¬ãã«ã®å¥å šæ§ãšããžãã¹ã¢ãŠããããã®å¥å šæ§ã¯å¥ç©ã§ããç£èŠã®å€å®åºæºã¯å¿ ããåœæ¥ã®ã¢ãŠãããããã«çœ®ããŠãã ããã
2. UNKNOWNïŒå€å®äžèœïŒãš UNHEALTHYïŒç°åžžïŒãå¥ã³ãŒãã§åãã
if [ -n "$UNKNOWN" ]; then
notify "â ïž SNSç£èŠãå€å®äžèœ: ${UNKNOWN}..."
exit 2
fi
äž¡è ãåãã¢ã©ãŒãã«æ··ãããšããå£ããŠãããã®ããç£èŠãå¹ããŠããªããã®ããåãããªããªããŸããç°ãªã exit code ãšç°ãªãçµµæåã§åºå¥ããããšã§ãåãåãåŽã®å¯ŸåŠãå€ãããŸãã
3. ç£èŠå¯Ÿè±¡ã®ã¬ãŒã³äžèЧãéç¥ã«å¿ ãå«ãã
REPORT 倿°ã«å
šã¬ãŒã³ã®ç¢ºèªçµæãç©ãã§éç¥ããŸãããäœã確èªã㊠healthy ãšèšã£ãŠãããããèŠããªãç£èŠã¯ã人éããå
šéšå€§äžå€«ããšèª€èªããæž©åºã«ãªããŸãã
4. ã¿ã€ã ã¹ã¿ã³ã圢åŒã¯å®éã®ãã°ãheadã§ç¢ºèªããŠããæ±ºãã
head -5 ~/.claude/logs/sns-ig-autopost.err.log
head -5 ~/dev/social-autolike/logs/autopost.log
head -5 ~/.claude/logs/tiktok-bokuwalily.log
ãJST/UTC/HUMANãã®3çš®é¡ãããããšãç¥ã£ãŠããŠããæ°ãããã°ã远å ãããã³ã«å®ç©ã確èªããç¿æ £ãã€ããŠãã ãããæ³å®ãšéã圢åŒã䜿ã£ãŠããããšãæ®éã«ãããŸãã
5. grep ã§ã¯ãªã awk ã§åœæ¥è¡ä»¥éã«çµã£ãŠã«ãŠã³ããã
ãã°ã¯è¿œèšåã§ããgrep -c "ããŒã«ãŒ" ã¯ãã¡ã€ã«å
šäœãæ€çŽ¢ããã®ã§åæ¥ä»¥åã®æåãæ°ããŸããawk ã§ seen ãã©ã°ã䜿ããåœæ¥ã®æ¥ä»è¡ãåºçŸããåŸã ããã«ãŠã³ã察象ã«ããŠãã ããã
6. pipefail ç°å¢ã® grep -c æ«å°Ÿã«ã¯ || true ãå¿
ãä»ãã
/usr/bin/grep -c 'ããã:0' || true
grep -c ã¯ããã0ä»¶ã®ãšã exit 1 ãè¿ããŸããpipefail ç°å¢ã§ã¯ããããã€ãå
šäœã®å€±æãšããŠæ±ãããã¹ã¯ãªãããéäžçµäºããŸãããå¥å
šãªæ¥ã»ã©ã¹ã¯ãªãããæ»ã¬ããšããé説çãã°ãé²ãã«ã¯ || true ã ãã§ååã§ãã
7. launchd ããèµ·åããã¹ã¯ãªããã®ã³ãã³ãã¯ãã¹ãŠçµ¶å¯Ÿãã¹ã§æžã
/usr/bin/grep -qF "$daymark" "$file"
/usr/bin/awk -v day="$daymark" ...
/usr/bin/python3 "$DISCORD" post alerts "$1"
launchd 㯠.zshrc ã .bash_profile ãèªã¿ãŸãããPATH ã« homebrew ã nvm ãå«ãŸããªãç¶æ
ã§èµ·åãããŸããçŽ ã®ã³ãã³ãåãæžããŠããŒã«ã«ã§åããŠããlaunchd çµç±ã§ã¯ãµã€ã¬ã³ãã«å€±æããŸãã
8. plist ã®ãã¹ã¯ãã«ãã¹ã§æžãïŒ~ 衚èšäžå¯ïŒ
StandardOutPathã»StandardErrorPathã»ProgramArguments ãã¹ãŠã« ~ ã䜿ããªãã§ãã ãããlaunchd ã¯ããŒã ãå±éããŸããã
9. RunAtLoad ã¯æ¬çª plist ã§ã¯ false ã«ãã
åäœç¢ºèªã¯ launchctl start ã©ãã«å ã§æåå®è¡ããŸããtrue ã«ãããš unload/load ã®ãã³ã«å³æå®è¡ãããåœæ¥è¡ãå°ãªãç¶æ
ã§ç£èŠãèµ°ã UNKNOWN ãå€çºããŸãã
10. notify() ã¯å€±æããŠã watchdog ãæ¢ããªã
notify() {
[ -f "$DISCORD" ] || return 0
/usr/bin/python3 "$DISCORD" post alerts "$1" >/dev/null 2>&1 \
|| log "discordéç¥ã«å€±æ(ãããã¯ãŒã¯?)"
}
æ·±å€ã®ãããã¯ãŒã¯æãDiscordã®ããŠã³ã§éç¥ãé£ã°ãªããŠããwatchdog ã¹ã¯ãªããèªäœã¯ç¶ç¶ãããŸããçµæã¯å¿ ããã°ã«æ®ããŸãã
11. 倿°ã¯ set -u ç°å¢ã§ãå®å
šãªåœ¢ã§å®£èšãã
FAILED=""
UNKNOWN=""
REPORT=""
ã¹ã¯ãªããå
é ã§ãã®3倿°ã空æåã§åæåããŠãã ããã${UNKNOWN:+$UNKNOWN,} æ§æã¯å€æ°ã宣èšãããŠããããšãåæã«ããŠããŸãã
12. æ°ã¬ãŒã³ã远å ãããå¿
ã launchctl start ã§æåå®è¡ããŠç¢ºèªãã
watchdog 㯠healthy ã®ãšãéç¥ãéããŸãããæ°ããã¬ãŒã³ãç£èŠç¶²ã«å
¥ã£ããã©ããã¯ãæåå®è¡åŸã« ok lane=æ°ã¬ãŒã³å ããã°ã«åºããã§ç¢ºèªããŸãã
13. bash -n ã§ã¹ã¯ãªãããäºåã«æ§æãã§ãã¯ããŠãã plist ãããŒããã
bash -n ~/.claude/scripts/sns-output-watchdog.sh && echo "syntax OK"
æ§æãšã©ãŒããããš watchdog 㯠launchd ãã°ã ãã«èšé²ãæ®ããŠãµã€ã¬ã³ãçµäºããŸããäºåã«æ§æãã§ãã¯ãéãç¿æ £ãã€ããããšã§ãplist ããŒãåŸã«ãç¡é³ãã§æ©ãæéãç¯çŽã§ããŸãã
14. éŸå€ã¯åœéœæ§çãèŠãªãã調æŽãã
count_dead_runs() ã®éŸå€ã3åãã¯ãå®ç°å¢ã§ã®åçºé害ïŒãããã¯ãŒã¯æã»ã»ãã·ã§ã³åãïŒãåžåã§ããå€ãšããŠèšå®ããŠããŸããç£èŠãå°å
¥ããŠããæåã®2é±éã¯éŸå€ã3ã«èšå®ããæ¯æ¥ã®ã¢ã©ãŒãçºççã芳å¯ããŠèª¿æŽããŠãã ãããæ¯æ¥ã¢ã©ãŒããé£ã¶ãªãéŸå€ãäžãããé害ãèŠéãé »åºŠãé«ããªãéŸå€ãäžãããæ°åãæ±ºããåã«å®æž¬ããŒã¿ãå¿
èŠã§ãã
15. ç£èŠã¹ã¯ãªããã®ç£èŠå¯Ÿè±¡ã宿çã«æ£åžããã
ä»åã®éå®³ã®æ ¹æ¬ã¯ãcontent-watchdog.sh ãå¥å šãšèšãç¶ãããããšã§ã¯ãªãããSNSæçš¿ã¬ãŒã³ãã©ã®watchdogã®ç£èŠå¯Ÿè±¡ã«ãå ¥ã£ãŠããªãã£ããããšã§ãããèªååã·ã¹ãã ã«æ°ããã¬ãŒã³ã远å ãããã³ã«ããã®ã¬ãŒã³ã¯ã©ã®watchdogãèŠãŠããããã確èªãããã§ãã¯ãªã¹ããæã€ããšã匷ãããããŸããæ1åã®ã¬ãã¥ãŒã§ãã2é±éã®æ²é»ã¯é²ããŸããã
ãŸãšã
SNSæçš¿ã14æ¥éæ¢ãŸã£ãŠããé害ããäœã£ãã®ã sns-output-watchdog.sh ãš com.lily.sns-output-watchdog.plist ã§ãããã®2ãã¡ã€ã«ãäœçŸããŠããããšã¯1è¡ã§èšããŸãââãã·ã¹ãã ãåããŠããããšããšãã¢ãŠãããããåºãŠããããšãã¯å¥ã®åœé¡ã§ãã
exit code ã§å¥å šæ§ãå€å®ãããšããæ£åžžçµäºãããææãªãããå»¶ã ãšã¹ã«ãŒããŸããåœæ¥ãã°ã«æåããŒã«ãŒãååšãããã§å€å®ããããšã§åããŠãããžãã¹çãªæå³ã§ã®ãåããŠããããæ€åºã§ããŸãã
ã¿ã€ã ã¹ã¿ã³ãã®æ··åšïŒJST/UTC/HUMAN圢åŒïŒãUNKNOWN ãš UNHEALTHY ã®åé¢ã空æåãšãŒãã®åºå¥ãgrep -c ãš pipefail ã®è¡çªââã©ãããå®éã«ãã°ãèŠããçµéšããæ¥ãŠããŸããã³ãŒãã®ã³ã¡ã³ãã« 2026-08-08 宿ž¬ã§ ig-1 ã¯12runäž8åã0ä»¶ ãšæžããŠããæ°åã¯æž¬å®å€ã§ãããããã®è©³çްããªããã°ãåãèœãšã穎ãäœåºŠã§ãèžã¿ãŸãã
ããã²ãšã€å€§äºãªããšãæžããŠãããŸãããwatchdog ã healthy ãšèšã£ãŠãããã¯ããã¹ãŠã®ã¬ãŒã³ãç£èŠãããŠããããæå³ããŸãããç£èŠã¹ã¯ãªããã¯èªåãç£èŠå¯Ÿè±¡ã«å«ããŠããã¬ãŒã³ããèŠãŸãããæ°ããèªååã远å ãããã³ã«ç£èŠç¶²ã®æŽæ°ãå¿ èŠã§ããã®æŽæ°ãæ ããšä»åã®ãããªãå¥å šã«èŠããé害ããèµ·ããŸãã
èªååã§çšŒããããã«ãªã£ãåã ãããã®èªååãç£èŠããä»çµã¿ãžã®æè³ãå¿ èŠã§ãã120äžã®å£²äžã®ãã¡SNSæµå ¥ãé¢ä¿ããéšåããã£ããèšç®ãããšã14æ¥ã®åæ¢ã¯ãããã8ã10äžååã®æ©äŒæå€±ã«çžåœããŸããç£èŠã¹ã¯ãªããã1æ¬æžãã³ã¹ããããæŸçœ®ã³ã¹ãã®æ¹ãã¯ããã«é«ãããã®èšäºãèªãã 仿¥ãèªåã®ã·ã¹ãã ã®ã©ã®ã¬ãŒã³ãã誰ãç£èŠããŠããªãããã確èªããããšãããããŸãã
ä»çµã¿ã®å
šäœåã»æ120äžã®å
èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã
ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- å¶äœç©ã»èšäºã¯ bokuwalily.com ã«ãŸãšããŠããŸãð¥ïž
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ