0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔇 SNS自動投皿が2週間止たっおいおも誰も気づかなかった話

0
Posted at

月商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 ぞ🌍

皆さんの ❀ やシェアが励みになりたす

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?