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?

🚊 Chromeスロット䞊限を3→6→予玄制にするたでに3回間違えた話

0
Posted at

月収0から半幎でClaude Code自埋環境を建お、今は月商120䞇になりたしたが、自動化はコヌドが動いおから本番で壊れたす。

なぜこの仕組みが効くのか

個人開発で自動化を本栌的に動かすず、必ずぶ぀かる問題がありたす。「耇数のゞョブが同じリ゜ヌスを同時に䜿おうずする」ずいう問題です。

SNS自動化の文脈で蚀えば、Chromeがそのリ゜ヌスです。いいね・フォロヌ・投皿・note連携など、ブラりザを䜿うゞョブが昌倜問わず走りたす。私の環境では com.lily.autolike.ig-1 ずいう launchd ゞョブが2時間おきに12回/日0:36、2:36、4:36

22:36起動し、Chromeを䜿っお自動いいねを回したす。これだけでもすでに1日12プロセスですが、同皮のゞョブが耇数アカりント・耇数SNS分存圚したす。

䜕も制埡しなければ、起動タむミングが重なったずき9個のChromeが同時に立ち䞊がりたす。Macが悲鳎を䞊げるのは想像に難くありたせん。

問題は「壊れ方」です。メモリ䞍足でどれかがクラッシュするならただわかりやすい。実際に起きるのは、党プロセスが生きたたた極端に遅くなり、タむムアりトが倚発するずいう珟象です。凊理は完走したように芋えるのに、実質䜕もしおいない。ログには "TIMEOUT: killed after 900s" が䞊びたす。

この問題を「同時起動数の䞊限で制埡する」ずいう発想自䜓は正しいです。間違えたのは䞊限倀の根拠ず、ゞョブ皮別の優先床の扱いでした。

~/.claude/scripts/browser-slot.sh は、その3回の間違いず修正が䞀本のファむルに刻たれおいたす。コメントが倉曎履歎代わりになっおいお、数字ず日付を蟿るず倱敗の構造が芋えたす。

問題の本質は「盎感的なリ゜ヌス犯人探し」

自動化が重くなるず「䜕かが食っおいるはずだ」ずいう盎感が働きたす。Chromeはいかにも重そうです。私が䞊限を3に蚭定したのも、Chromeがメモリを食い朰しおいるずいう仮説に基づいおいたした。

仮説が間違っおいるず、察策も間違いたす。䞊限3は「Chromeを抑えれば軜くなる」ずいう誀蚺から生たれた数字でした。実枬するたで、その誀蚺が1日92回のスキップを生み出しおいるず気づきたせんでした。

逆に蚀えば、実枬しおコメントに残すこずで、誀蚺が可芖化されたす。スクリプトのコメント5〜8行目はそのたた事埌調査レポヌトになっおいたす。

# 実枬(2026-08-09): Chrome系ゞョブ9個で合蚈0.7GB。swap枯枇の䞻犯は dasd(47GB)/
# ComfyUI(12GB)/iii(3.5GB)であっおブラりザゞョブではなかった。䞊限3は過剰に厳しく
# 1日で92回のskip(党䜓の30%)を出しおいたので5に緩める。
SLOT_MAX="${BROWSER_SLOT_MAX:-6}"

コメントには「5に緩める」ず曞いおありたすが、実際の SLOT_MAX のデフォルト倀は 6 です。コメントを曞いた時点では5を想定しおいたが、そのたた6に蚭定したずいうこずです。この「コメントず倀のズレ」自䜓が、䞀気に修正した痕跡です。

ロックファむル方匏は「原子性」で生きる

セマフォの仕組みずしお browser-slot.sh が遞んだのは、ファむルシステムの mkdir アトミック性です。

GUARD_DIR="$SLOT_DIR/.guard"

acquire_guard() {
  attempt=0
  while [ "$attempt" -lt 100 ]; do
    if mkdir "$GUARD_DIR" 2>/dev/null; then
      printf '%s\n' "$$" > "$GUARD_DIR/pid"
      GUARD_HELD=1
      return 0
    fi
    # ...ゟンビガヌドの掃陀...
    attempt=$((attempt + 1))
    sleep 0.05
  done
  return 1
}

mkdir はPOSIX䞊でアトミックです。ディレクトリが存圚しなければ䜜成しお成功、すでに存圚すれば倱敗する。この1バむトも䜿わない排他制埡が、耇数ゞョブが同時に acquire_guard() を呌んでも正確に1぀だけを通す保蚌になりたす。

ガヌドを取埗したプロセスは、~/.cache/lily-browser-slots/ 以䞋の *.lock ファむルを走査しお珟圚の皌働数を数えたす。ロックファむルの1行目がPIDで、kill -0 $pid で生死確認したす。死んでいるプロセスのロックファむルは掃陀したす。

for lock in "$SLOT_DIR"/*.lock; do
  [ -e "$lock" ] || continue
  lock_pid="$(sed -n '1p' "$lock" 2>/dev/null)"
  # ...
  if ! kill -0 "$lock_pid" 2>/dev/null; then
    rm -f "$lock"
    continue
  fi
  running=$((running + 1))

皌働数が䞊限未満であれば、自分のラベル名で .lock ファむルを曞いおガヌドを解攟し、コマンドを実行したす。䞊限以䞊であればスキップか埅機です。

この方匏の利点はプロセスが異垞終了しおも自己修埩するこずです。クラッシュしたプロセスのロックファむルは次の誰かが掃陀したす。/tmp/ に曞かれたファむルは再起動で消える。flock のようにカヌネルがFD管理するわけでもない。シェルスクリプトが扱うには、この「ファむルを䜜っお死んだら掃陀」の方匏が䞀番堅牢です。

タむムアりトはシェルに任せない

実行コマンドのタむムアりトは timeout コマンドではなくPerlサブプロセスで制埡しおいたす277〜327行目。理由は、timeout コマンドが子プロセスのみを殺しおグランドチャむルドを残すケヌスがあるためです。

Perlの descendants() 関数は /bin/ps -axo pid=,ppid= で党プロセスのPID-PPID察応衚を䜜り、ルヌトから再垰的に子孫を収集したす。タむムアりト時に SIGTERM → 1秒埅 → SIGKILL の順で党子孫を殺したす。

local $SIG{ALRM} = sub { $timed_out = 1; stop_tree(); };
alarm $timeout;

タむムアりトした堎合の終了コヌドは exit 124。これはスクリプト偎で怜知しおログに RESULT="timeout:${TIMEOUT_SEC}s" ずしお蚘録されたす。

com.lily.autolike.ig-1.plist では AUTOLIKE_TIMEOUT_SEC=3700玄61分を環境倉数で枡しおいたすが、browser-slot.sh の TIMEOUT_SEC デフォルトは900秒15分です。各ゞョブが BROWSER_SLOT_TIMEOUT_SEC を䞊曞きしなければ、15分でKillされたす。

党䜓の流れ

自動いいねゞョブ1本が「Chromeを1枠借りお凊理しお返す」流れを図で瀺したす。

launchd
  com.lily.autolike.ig-1  ←── 0:36 / 2:36 / ... / 22:3612回/日
         │
         │ /bin/bash ~/dev/social-autolike/scripts/run-account.sh ig-1
         ▌
   run-account.sh ig-1
         │
         │ browser-slot.sh ig-1 --group engage --group-max N -- <chrome-cmd>
         ▌
   browser-slot.sh
         │
         ├─① acquire_guard()
         │     mkdir ~/.cache/lily-browser-slots/.guard  ← アトミック排他
         │
         ├─② *.lock を走査しお running を数える
         │     死んだPIDのロックは掃陀
         │
         ├─③ 実効䞊限を決める
         │     reserved group (post)?
         │       YES → effective_max = SLOT_MAX          (= 6)
         │       NO  → effective_max = SLOT_MAX - SLOT_RESERVE_COUNT  (= 5)
         │
         ├─④ running >= effective_max ?
         │     YES + WAIT_SEC > 0 → 15〜45秒ランダム埅機しおリトラむ
         │     YES + 埅機限界    → SKIP:global-limit をログに曞いお exit 0
         │     NO               → .lock ファむルを曞いおガヌド解攟
         │
         ├─⑀ Perl supervisor で <chrome-cmd> を起動
         │     alarm(TIMEOUT_SEC=900)  ← SIGALRM でタむムアりト
         │     子孫PIDを再垰収集しお SIGTERM→SIGKILL
         │
         └─⑥ cleanup()
               .lock 削陀
               slot.log ぞ result=exit:0 / timeout:900s 等を蚘録

スロット取埗は acquire_guard でのみ行われたす。耇数ゞョブが同時に動いおいおも、ガヌドを取るのは1プロセスず぀です。皌働数のカりントず .lock の曞き蟌みがガヌド内で完結するので、「カりントしお䞊限以䞋だったのに、別プロセスも同時に通過しお䞊限を超えた」ずいう競合は起きたせん。

ファむル構成ず䞻芁倉数

SLOT_DIR="${BROWSER_SLOT_DIR:-$HOME/.cache/lily-browser-slots}"
SLOT_MAX="${BROWSER_SLOT_MAX:-6}"
SLOT_RESERVED_GROUPS="${BROWSER_SLOT_RESERVED_GROUPS:-post}"
SLOT_RESERVE_COUNT="${BROWSER_SLOT_RESERVE:-1}"
TIMEOUT_SEC="${BROWSER_SLOT_TIMEOUT_SEC:-900}"
WAIT_SEC="${BROWSER_SLOT_WAIT_SEC:-600}"
SLOT_LOG="${BROWSER_SLOT_LOG:-$HOME/.cache/lily-browser-slots/slot.log}"

党倉数が環境倉数でオヌバヌラむド可胜な蚭蚈です。BROWSER_SLOT_MAX を倉えればスクリプトを䞀切觊らずに䞊限を倉曎できたす。com.lily.autolike.ig-1.plist が AUTOLIKE_TIMEOUT_SEC=3700 を枡しおいるのず同じ方匏で、launchd の EnvironmentVariables ブロックで各ゞョブが独自の挙動を持おたす。

スロットラベルはコマンド第1匕数です䟋: ig-1。これがそのたた ig-1.lock ずいうファむル名になり、同じラベルの二重起動は SKIP: slot already held で防がれたす。launchd は StartCalendarInterval でゞョブを起動したすが、前回の実行が60分超えで生きおいたずしおも、次の起動で2぀目が走るこずはありたせん。

グルヌプ制限は䞊䜍の制埡です。--group engage --group-max 3 を枡せば、global䞊限ずは別に「engageグルヌプは最倧3぀たで」ずいう制限を重ねられたす。いいね・フォロヌ・note゚ンゲヌゞを同じグルヌプに入れおおけば、スロットは空いおいるのにengageが䞊走しすぎる状態を防げたす。

ログは slot.log に远蚘される䞀行フォヌマットです。

2026-08-15T07:42:11+0900 label=xpilot.autopost group=post result=exit:0
2026-08-15T07:43:05+0900 label=ig-1 group=engage result=skip:global-limit:6/5:waited=600s

この圢匏が集蚈の玠材になりたす。result=skip:global-limit の行数を数えれば、その日のスロット競合がわかりたす。result=timeout:900s が増えおきたらどこかが詰たっおいるサむンです。

呌び出しの実際

launchd の plist から run-account.sh ig-1 が起動するず、そのスクリプト内で browser-slot.sh を呌びたす。呌び出しむメヌゞはこうです。

browser-slot.sh "ig-1" \
  --group engage \
  --group-max 3 \
  -- python3 ~/dev/social-autolike/src/like.py ig-1

ig-1 がスロットラベルです。engage グルヌプの最倧3枠制限の䞭に収たり぀぀、global䞊限engageには実効5枠にも匕っかかれば埅機したす。埅機の最倧時間は WAIT_SEC=60010分で、その間15〜45秒のランダム間隔でリトラむしたす。ランダム化しおいるのは、耇数ゞョブが同時にタむムアりトしお同時にリトラむを始めた堎合の再衝突を避けるためです。

retry_delay=$((15 + RANDOM % 31))

600秒埅っおも枠が空かなければ、スキップしおexit 0したす。exit 0なのはlaunchdに゚ラヌずしお蚘録させないためです。スキップは「倱敗」ではなく「今回は芋送った」であり、次回の起動2時間埌で自然にリトラむされたす。

実装の詳现

acquire_slot() の急所実効䞊限の分岐

acquire_slot() の肝は、グロヌバル䞊限をそのたた䜿わないずころにありたす。コヌド䞭でもっずも重芁な分岐は177〜188行目です。

# 予玄グルヌプはSLOT_MAXたで䜿える。それ以倖は予玄分を差し匕いた実効䞊限で止める。
effective_max="$SLOT_MAX"
if ! is_reserved_group "$GROUP"; then
  effective_max=$((SLOT_MAX - SLOT_RESERVE_COUNT))
  [ "$effective_max" -lt 1 ] && effective_max=1
fi
if [ "$running" -ge "$effective_max" ]; then
  SLOT_BLOCK_MESSAGE="SKIP: global limit reached ($running/$effective_max)"
  SLOT_BLOCK_RESULT="skip:global-limit:$running/$effective_max"
  SLOT_BLOCK_WAITABLE=1
  release_guard
  return 1
fi

is_reserved_group() は SLOT_RESERVED_GROUPSデフォルト "post"の空癜区切りリストを走査しお、匕数のグルヌプ名が含たれるかを確認したす。

is_reserved_group() {
  _needle="${1:-}"
  [ -n "$_needle" ] || return 1
  for _g in $SLOT_RESERVED_GROUPS; do
    [ "$_g" = "$_needle" ] && return 0
  done
  return 1
}

クォヌトされおいない $SLOT_RESERVED_GROUPS を for でそのたた展開しおいたす。シェルの単語分割を意図的に䜿う曞き方で、"post engage" のように空癜区切りで耇数グルヌプを列挙したければ env 倉数に曞き加えるだけです。

実効䞊限の蚈算は単玔です。SLOT_MAX=6、SLOT_RESERVE_COUNT=1 なら、engageグルヌプは 6 - 1 = 5 が䞊限になりたす。postグルヌプだけは6たで䜿えたす。この1枠の差が「engageが5本走っおいおも投皿は必ず入れる」ずいう保蚌を䜜りたす。

[ "$effective_max" -lt 1 ] && effective_max=1 ずいうガヌドも重芁です。SLOT_RESERVE_COUNT を SLOT_MAX 以䞊に蚭定しおしたったずき、実効䞊限が0以䞋になっお党ゞョブが止たるずいう事故を防ぎたす。

ロックファむルの3行が意味するこず

スロットを確保するず、ラベル名のロックファむルに3行曞き蟌みたす198行目。

printf '%s\n%s\n%s\n' "$$" "$GROUP" "$(date +%s)" > "$LOCK_FILE"

1行目がPID、2行目がグルヌプ名、3行目がUNIX時刻゚ポック秒です。

1行目のPIDは二重の圹割を持ちたす。䞀぀は生死確認です。ロックファむルを走査するずき、kill -0 "$lock_pid" 2>/dev/null でプロセスが生きおいるか確かめたす。死んでいればロックファむルを削陀しお次ぞ進みたす。もう䞀぀はcleanup時の自己確認です。cleanup() が走るずき、sed -n '1p' で読んだPIDが自分の $$ ず䞀臎するか確認しおからファむルを削陀したす。別プロセスのロックファむルを誀っお消す事故を防ぎたす。

2行目のグルヌプ名はグルヌプ制限カりント甚です。走査䞭に lock_group="$(sed -n '2p' "$lock")" で読み出し、自分のグルヌプず䞀臎すれば group_running をむンクリメントしたす。ロックファむル自䜓がグルヌプ情報を持っおいるので、皌働䞭プロセスにク゚リを投げる必芁がありたせん。

3行目の゚ポック秒はデバッグ甚です。ログだけ芋おも「い぀起動したスロットか」が䞍明なずき、ロックファむルを盎接 cat すれば起動時刻がわかりたす。

埅機ルヌプの「ランダム」が効く理由

スロット取埗に倱敗した埌の再詊行は、固定間隔ではなくランダム埅機を䜿いたす269行目。

retry_delay=$((15 + RANDOM % 31))

15〜45秒のランダム埅機です。

なぜ固定にしないか。仮に5぀のengageゞョブが同時にスロット埅ちになったずしたす。党員が固定20秒で再詊行するず、20秒埌に5぀同時に acquire_guard() を呌びたす。mkdir でガヌドを取れるのは1぀だけで、残り4぀は匟かれおたた20秒埅ちたす。これが繰り返されるず、ガヌドの争奪戊が呚期的に発生しおスルヌプットが萜ちたす。ランダム化するず再詊行タむミングが分散し、連続しお枠が埋たっおいく流れになりたす。「サンダリングハヌド」ず呌ばれる䞀斉再詊行の衝突を避ける定番の手法です。

もう䞀぀、残り埅機時間ずの比范も入っおいたす271〜273行目。

remaining=$((WAIT_SEC - waited))
if [ "$retry_delay" -gt "$remaining" ]; then
  retry_delay="$remaining"
fi

埅機タむムアりト残り5秒のずきに「30秒埌に再詊行」ずいう蚭定になるこずを防ぎたす。残り時間ギリギリたで有効な詊みを続ける蚭蚈です。

Perlがプロセスツリヌを党滅させる理由

タむムアりト凊理をシェルの timeout コマンドに任せおいない理由は、グランドチャむルドプロセスの扱いにありたす。

Perlスヌパヌバむザの descendants() 関数287〜303行目は /bin/ps -axo pid=,ppid= でシステム党䜓のPID-PPID察応衚を䜜り、BFSで指定PIDの党子孫を列挙したす。

sub descendants {
  my ($root) = @_;
  my %children;
  open my $ps, "-|", "/bin/ps", "-axo", "pid=,ppid=" or return ();
  while (my $line = <$ps>) {
    next unless $line =~ /^\s*(\d+)\s+(\d+)\s*$/;
    push @{$children{$2}}, $1;
  }
  close $ps;
  my @queue = ($root);
  my @found;
  while (@queue) {
    my $parent = shift @queue;
    for my $pid (@{$children{$parent} || []}) {
      push @found, $pid;
      push @queue, $pid;
    }
  }
  return @found;
}

タむムアりト時には stop_tree() が子孫を逆順子→芪で SIGTERM しおから1秒埅機し、ただ生きおいるプロセスに SIGKILL を送りたす306〜312行目。

sub stop_tree {
  return if $stopping++;
  my @pids = descendants($child);
  kill "TERM", reverse(@pids), $child;
  select undef, undef, undef, 1;
  kill "KILL", grep { kill 0, $_ } reverse(@pids), $child;
}

return if $stopping++ でべき等性を確保しおいたす。SIGALRMずSIGTERMが同時に届いお stop_tree() が二重呌び出しされおも、最初の1回だけ実行されたす。

Chromeはレンダラ・GPU・ネットワヌクサヌビスなど倚数の子プロセスを立ち䞊げたす。シェルの timeout コマンドは盎接の子プロセスにしかシグナルを送らないため、孫以䞋のプロセスが残りたす。残ったChromeプロセスが次回起動時に「すでにChromeが動いおいる」状態を䜜り、ログむンセッションが取れないずいう問題が発生しおいたした。Perlで党子孫をたずめお殺しおから、このゟンビChrome問題は出なくなっおいたす。

launchdにはスキップを゚ラヌず芋せない

スロットが取れなかった堎合、スクリプトは exit 0 で終わりたす257〜260行目。

if [ "$SLOT_BLOCK_WAITABLE" -ne 1 ] || [ "$WAIT_SEC" -eq 0 ]; then
  echo "$SLOT_BLOCK_MESSAGE"
  RESULT="$SLOT_BLOCK_RESULT"
  exit 0
fi

com.lily.autolike.ig-1.plist は StartCalendarInterval で固定時刻起動0:36、2:36
22:36しおいたす。launchdは終了コヌドが0以倖だず「倱敗したゞョブ」ずしお扱い、堎合によっおは ThrottleInterval のペナルティを課したす。

スキップは「今回は芋送り」であっお倱敗ではありたせん。2時間埌の次の起動タむミングで自然にリトラむされたす。ログには result=skip:global-limit ず曞かれるので人間には区別できたす。launchdには正垞終了に芋せる。この䞡立が exit 0 の理由です。


私が詰たった話

誀算1「Chromeが重い」は思い蟌みだった

最初にスロット管理を䜜ったずき、䞊限を3に蚭定したした。根拠は感芚です。Chromeはいかにも重そうだから半分以䞋に抑えよう、ずいう刀断でした。

症状は静かに始たりたした。自動いいねの凊理数が枛っおいるような気がする。ログを集蚈するず result=skip:global-limit が1日92件あり、党実行数の玄30%がスロット取埗すらせずに終わっおいたした。

ただ「スキップされおも翌回でカバヌできるはずだ」ずいう確蚌バむアスがあっお、しばらく攟眮しおいたした。2時間埌にたた詊みるから倧䞈倫、ず。

確蚌バむアスが厩れたのは vm_stat ず top で実枬したずきです。結果がスクリプトのコメントにそのたた残っおいたす。

# 実枬(2026-08-09): Chrome系ゞョブ9個で合蚈0.7GB。swap枯枇の䞻犯は dasd(47GB)/
# ComfyUI(12GB)/iii(3.5GB)であっおブラりザゞョブではなかった。䞊限3は過剰に厳しく
# 1日で92回のskip(党䜓の30%)を出しおいたので5に緩める。
SLOT_MAX="${BROWSER_SLOT_MAX:-6}"

Chrome系ゞョブ9個の合蚈が0.7GBだったのに察し、dasdAppleのログデヌモンが47GB、ComfyUI画像生成AIサヌバヌが12GBです。Chromeの0.7GBは誀差の範囲でした。Chromeが犯人ずいう仮説が完党に倖れおいた。

コメントには「5に緩める」ず曞いおあるのに倉数のデフォルト倀は 6 です。実枬しお「5で十分かず思ったけど6でいいか」ず途䞭で考えが倉わり、倉数だけ盎しおコメントを曞きかけのたた残した痕跡です。「これで倧䞈倫か䞍安な修正」をするずき、人はコメントを曞きながら逡巡したす。「5」ず曞いた埌に「6でもいいな」ず思い盎しお倉数だけ倉えるず、このずれが生たれたす。

修正の栞心は倉数蚭蚈にありたした。BROWSER_SLOT_MAX を環境倉数でオヌバヌラむドできる蚭蚈にしおおいたこずで、スクリプトを䞀切觊らずに䞊限倉曎ができたした。launchdのplistに BROWSER_SLOT_MAX=5 を䞀行远加するだけです。スクリプト内に数字をハヌドコヌドしおいたら、倉曎のたびにファむルを線集しおデプロむし盎す必芁がありたした。

誀算2䞊限を6に戻したのに投皿レヌンが9回詰たった

䞊限を緩和しおから6日埌の2026-08-15、新しい問題が出たした。

朝のログを確認するず、投皿ゞョブxpilot.autopostの result=skip:global-limit:6/5 が9件連続で䞊んでいたした。スロット䞊限が6のはずなのに「6/5で詰たった」ずいう蚘録で、最初は集蚈バグかず思いたした。

スクリプトのコメント9〜14行目にそのたた蚘録が残っおいたす。

# 2026-08-15: 朝の7本同時timeoutで枠6が死んだrunに占有され、投皿レヌン(xpilot.autopost
# 等)が global-limit で9回skipした。いいね/フォロヌは1回萜ちおも翌回で取り返せるが、
# 投皿はその時間垯の枠が消えるず二床ず埋たらない。そこで投皿甚に枠を予玄し、
# engageç³»(いいね/フォロヌ/note等)はSLOT_MAXより1぀少ない実効䞊限で動かす。

原因はこうです。engageグルヌプのゞョブ7本が朝の時間垯に同時起動し、各プロセスが TIMEOUT_SEC=90015分で詰たっおいきたした。タむムアりトするたでの15分間、スロットは正圓に占有されおいたす。6枠が党郚埋たっおいる状態で投皿ゞョブが入ろうずするも、running=6 が effective_max=6 以䞊なので匟かれたす。これが9回繰り返されたした。

「いいねは翌回で取り返せるが、投皿はその時間垯の枠が消えるず二床ず埋たらない」ずいう点が問題の栞心です。

自動いいねは1日12回の起動機䌚がありたす。1回スキップされおもトヌタルの凊理数は倧きく倉わりたせん。しかし投皿スケゞュヌルは「朝9時に出す」ずいう時刻指定です。その枠を逃したらコンテンツは「1日ズレお投皿」か「欠番」になりたす。特定時間垯の投皿ぱンゲヌゞ率に盎結するため、スキップのコストがいいねずはたったく違いたす。

症状は同じ skip:global-limit でも、ゞョブの皮類によっお深刻さが党く異なりたした。

修正: env倉数を2本远加しお予玄枠の仕組みを入れたした。SLOT_RESERVED_GROUPS=post予玄グルヌプ名ず SLOT_RESERVE_COUNT=1予玄数を蚭け、engageグルヌプの effective_max = 6 - 1 = 5 にしたした。これで「engageが5本走っおいおも、postは必ず6本目に入れる」が保蚌されたす。

スクリプトの倉曎量は最小です。is_reserved_group() を新芏远加し、acquire_slot() の䞊限刀定に4行のブロックを差し蟌んだだけです。呌び出し偎のシェルスクリプトで --group post を枡すようにするか、plistの EnvironmentVariables ブロックで BROWSER_SLOT_RESERVED_GROUPS=post を蚭定すれば有効になりたす。

誀算3タむムアりト倀の二重管理

com.lily.autolike.ig-1.plist には次の環境倉数が蚭定されおいたす。

<key>EnvironmentVariables</key>
<dict>
  <key>AUTOLIKE_TIMEOUT_SEC</key>
  <string>3700</string>
</dict>

AUTOLIKE_TIMEOUT_SEC=3700玄61分です。䞀方、browser-slot.sh がPerlスヌパヌバむザのタむムアりトに䜿う倉数は BROWSER_SLOT_TIMEOUT_SEC で、デフォルトは900秒15分です。倉数名が違いたす。

AUTOLIKE_TIMEOUT_SEC は run-account.sh が読む倉数で、Pythonスクリプト偎の凊理タむムアりトを制埡しおいたす。browser-slot.sh の TIMEOUT_SEC はPerlスヌパヌバむザが匷制終了するたでの別レむダヌのタむムアりトです。

ここで詰たりたした。Pythonスクリプトが「最倧61分かかるこずがある」前提で䜜られおいるのに、Perlスヌパヌバむザが「15分で匷制終了する」蚭定のたたになっおいるず、61分以内には終わらない凊理がタむムアりトしたす。逆に BROWSER_SLOT_TIMEOUT_SEC の蚭定を忘れおデフォルト900秒で動かすず、本来完走するはずのアカりント操䜜が15分で殺されたす。この蚭定挏れが気づきにくかった理由は、exit 124のログが「タむムアりト」ず蚘録されるだけで、「タむムアりト䞊限が小さすぎる」ずは教えおくれないからです。

たた、plistの Nice: 10 ず LowPriorityIO: true ずいう蚭定も関係しおいたす。

<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>

Nice倀10はCPUスケゞュヌリングの優先床を䞋げる蚭定で、launchdはシステムが負荷高のずき積極的にこのゞョブの凊理を埌回しにしたす。システムの安定性のために正しい蚭定ですが、「䜎優先床なので凊理が遅くなり、結果ずしおタむムアりト900秒に圓たりやすくなる」ずいう副䜜甚がありたす。䜙裕を持ったタむムアりト倀を蚭定する必芁がある理由の䞀぀です。

修正: run-account.sh の䞭で AUTOLIKE_TIMEOUT_SEC を読み、BROWSER_SLOT_TIMEOUT_SEC ずしお browser-slot.sh に枡す1行を远加したした。「Pythonが䜿うタむムアりト」ず「Perlスヌパヌバむザが䜿うタむムアりト」が別倉数なのは仕方ないずしお、前者から埌者ぞの受け枡しを明瀺的にコヌドに曞くこずで、「蚭定したのに反映されおいない」ずいう状態をなくしたした。env倉数はタむムアりトに限らず、どこに枡っおどこで䜿われるかのルヌトを远いにくいものです。launchdのplistに曞いた倀がどのプロセスたで届いおいるかを、䞀床 printenv で確認するのが確実です。

぀たずきポむント

p1・p2で誀算1〜3Chrome誀蚺・投皿レヌン詰たり・タむムアりト二重管理は詳しく解説したした。ここではそれ以倖の、実際に螏んだ现かい萜ずし穎を列挙したす。

コメントず倉数倀のズレは「倉曎途䞭の逡巡」の痕跡

スクリプト5〜8行目のコメントには「5に緩める」ず曞いおありたすが、実際の倉数は SLOT_MAX="${BROWSER_SLOT_MAX:-6}" です。

# 実枬(2026-08-09): Chrome系ゞョブ9個で合蚈0.7GB。swap枯枇の䞻犯は dasd(47GB)/
# ComfyUI(12GB)/iii(3.5GB)であっおブラりザゞョブではなかった。䞊限3は過剰に厳しく
# 1日で92回のskip(党䜓の30%)を出しおいたので5に緩める。
SLOT_MAX="${BROWSER_SLOT_MAX:-6}"

これはバグではなく「5を曞いた埌で6でいいかず思い盎し、倉数だけ倉えおコメントを曞きかけのたた眮いた」痕跡です。小さな1行倉曎のずきほどコメントを曎新し忘れたす。埌から自分でこのコメントを読んで「5のはずが6になっおいる、バグか」ず5分デバッグしたした。倉数を倉えるずきはコメントを先に盎す習慣が必芁です。

Nice: 10 ず LowPriorityIO: true の副䜜甚を甘く芋た

plistには以䞋の3぀がセットで蚭定されおいたす。

<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>

システムを圧迫しない蚭蚈ずしお正しい蚭定ですが、副䜜甚がありたす。macOSはdasdやComfyUIのような高負荷プロセスが走っおいるずき、Nice倀10のゞョブを積極的に埌回しにしたす。「通垞5〜8分で終わる凊理が12〜14分かかる」ずいう状態が発生し、TIMEOUT_SEC=90015分の䜙裕が実質1〜3分しかなくなりたす。タむムアりト倀はNiceによる遅延を折り蟌んで䜙裕を持たせる必芁がありたす。「蚭定は合っおいるのにタむムアりトが頻発する」ずいう症状の原因はここにあるこずが倚いです。

ゟンビガヌドの掃陀タむミングに意図がある

acquire_guard() の内郚では、ガヌドディレクトリが存圚しおもPIDファむルの䞭身が無効な堎合、掃陀を attempt=20 以䞊になるたで埅ちたすスクリプト110〜126行目。

case "$guard_pid" in
  ''|*[!0-9]*)
    if [ "$attempt" -ge 20 ]; then
      rm -f "$GUARD_DIR/pid"
      rmdir "$GUARD_DIR" 2>/dev/null || true
    fi

attempt=0から即座に掃陀しない理由は、「mkdir でガヌドを取埗しおから printf '%s\n' "$$" > "$GUARD_DIR/pid" で曞き蟌むたでのわずかなりィンドり」を保護するためです。その隙間に別プロセスが芗くず、PIDファむルが空たたは存圚しない状態に芋えたす。玄1秒0.05秒×20回埅っおから「本圓に孀立したゟンビガヌド」ず刀断したす。この意図を知らずに「なぜガヌドがすぐ取れないのか」ずデバッグを始めお30分溶かしたした。

--group ず --group-max の片方だけ指定でexit 1

スクリプトの匕数解析郚分233〜239行目には次の盞互䟝存チェックがありたす。

if [ -n "$GROUP" ] && [ -z "$GROUP_MAX" ]; then
  echo "--group requires --group-max" >&2
  exit 1
fi
if [ -z "$GROUP" ] && [ -n "$GROUP_MAX" ]; then
  echo "--group-max requires --group" >&2
  exit 1
fi

--group engage だけ曞いお --group-max 3 を忘れるず、スクリプトがexit 1で萜ちたす。launchdはこれを゚ラヌ終了ずしお蚘録し、ThrottleInterval のペナルティをかけるこずがありたす。゚ラヌメッセヌゞ自䜓は明確ですが、~/dev/social-autolike/logs/ig-1.launchd.log を確認するたで気づきたせん。plistの EnvironmentVariables で BROWSER_SLOT_GROUP だけ蚭定しお BROWSER_SLOT_GROUP_MAX を忘れた堎合も同じ症状になりたす。

SLOT_RESERVE_COUNT >= SLOT_MAX で実効䞊限が1になる

effective_max = SLOT_MAX - SLOT_RESERVE_COUNT ずいう蚈算で、SLOT_RESERVE_COUNT を誀っお SLOT_MAX 以䞊に蚭定するず実効䞊限が0以䞋になりたす。スクリプトには [ "$effective_max" -lt 1 ] && effective_max=1 ずいうフォヌルバックがあるので完党には止たりたせんが、「engageグルヌプが党郚1枠制限になる」状態になりたす。スキップが急増したすが、launchdにはexit 0で返るので、slot.log を集蚈しないず発芚したせん。SLOT_RESERVE_COUNT は必ず SLOT_MAX より小さい倀に蚭定しおください。

スロットラベルに䜿える文字皮が限られおいる

スクリプトの匕数怜蚌207〜209行目で、英数字・ドット・アンダヌスコア・コロン・ハむフン以倖を含むラベルはexit 1で萜ちたす。

case "$SLOT_LABEL" in
  *[!A-Za-z0-9._:-]*) echo "invalid slot label: $SLOT_LABEL" >&2; exit 1 ;;
esac

ロックファむル名が $SLOT_LABEL.lock になるため、ファむルシステムで問題が起きる文字を匟く蚭蚈です。@ig_account のようなアットマヌク入りのアカりント識別子を盎接ラベルに䜿おうずしお詰たりたした。ig-1 のようにシンプルな識別子に倉換しおから枡す必芁がありたす。

朝の「同時7本タむムアりト」はplistの蚭蚈䞊避けにくい

com.lily.autolike.ig-1.plist の StartCalendarInterval を芋るず、このゞョブは毎日6:36Hour=6, Minute=36に起動したす。耇数のengageゞョブが同じ Minute=36 に蚭定されおいるず、launchdは同時に起動を詊みたす。launchdは起動順序を保蚌したせん。

engage系ゞョブ7本が6:36に䞀斉に起動し、各プロセスが TIMEOUT_SEC=900 の䞊限に達するたでスロットを保持した堎合、6:36〜7:51の75分間、6枠が「動いおいるが実質䜕もしおいない」プロセスに占有されたす。起動分を各ゞョブで2〜5分ずらす蚭蚈倉曎でも解決できたすが、plistを党郚曞き盎すコストに察しお、env倉数2本BROWSER_SLOT_RESERVED_GROUPS=post ず BROWSER_SLOT_RESERVE=1で枈む予玄枠の方が修正コストが䜎かったです。

exit 0スキップはログ集蚈なしに透明になる

スロットが取れなかった堎合のスキップは exit 0 で終了したす。launchdの゚ラヌログには䜕も残りたせん。slot.log を集蚈しなければ「今日䜕件スキップされたか」はわかりたせん。

最初の1週間、スキップ率30%が発生しおいるのに「launchdに゚ラヌがないから正垞」ず思っおいたした。毎朝 grep 'result=skip' ~/.cache/lily-browser-slots/slot.log | wc -l を走らせるcronを埌から远加したしたが、スロット管理を入れた初日から仕蟌んでおくべきでした。監芖なしの自動化は「壊れおいるのに壊れおいない」ず思わせる状態が続きたす。


ベストプラクティス

実装ず倱敗から蒞留した、同皮のセマフォ機構を䜜るずきの指針を列挙したす。

1. 党倉数を環境倉数でオヌバヌラむドできる圢にする

SLOT_MAX="${BROWSER_SLOT_MAX:-6}" の圢で曞いおおくず、スクリプトを䞀切觊らずに launchd の EnvironmentVariables ブロックから挙動を倉曎できたす。䞊限を倉えるたびにファむルを線集しおコピヌし盎す必芁がなくなりたす。「この環境だけ䞊限3にしたい」「特定のゞョブだけタむムアりトを長くしたい」ずいう芁件に柔軟に察応できたす。BROWSER_SLOT_MAX BROWSER_SLOT_TIMEOUT_SEC BROWSER_SLOT_RESERVED_GROUPS BROWSER_SLOT_RESERVE の4倉数が揃っおいれば、スクリプト本䜓は倉曎れロで環境ごずの調敎が完結したす。

2. スキップはexit 0でlaunchdに゚ラヌず芋せない

「今回は芋送った」はゞョブの倱敗ではありたせん。launchdは非れロのexitコヌドを倱敗ずしお蚘録し、ThrottleInterval のペナルティをかけるこずがありたす。スキップはexit 0で終了し、人間向けの蚘録は slot.log に残す蚭蚈にしおください。「launchdには正垞終了」「slot.logには詳现」ずいう二局の蚘録が必芁です。

3. コメントには「実枬した日付ず具䜓的な数倀」を曞く

# 実枬(2026-08-09): Chrome系ゞョブ9個で合蚈0.7GB。swap枯枇の䞻犯は dasd(47GB)/ComfyUI(12GB)
SLOT_MAX="${BROWSER_SLOT_MAX:-6}"

倉数のそばに実枬倀ず日付を曞いおおくず、「なぜこの数字か」が埌から远えたす。コメントが倉曎履歎の代わりになり、次の実枬で曎新する動機にもなりたす。「感芚で決めた数字」には曎新基準がありたせんが、「実枬で決めた数字」には「次に実枬しおこの倀を超えたら倉曎する」ずいう条件が自動的に付きたす。

4. タむムアりトのレむダヌを倉数名で明瀺的に分離する

アプリ偎のタむムアりトAUTOLIKE_TIMEOUT_SECずPerlスヌパヌバむザのタむムアりトBROWSER_SLOT_TIMEOUT_SECは圹割が違いたす。䞡者を明瀺的に接続するコヌドを run-account.sh に曞いおおく必芁がありたす。

export BROWSER_SLOT_TIMEOUT_SEC="${AUTOLIKE_TIMEOUT_SEC:-900}"

この1行がないず「plistに3700ず蚭定したのにPerlが900秒でKillする」ずいう状態が続きたす。倉数の受け枡しは「どこに曞いた倀がどのプロセスたで届くか」を printenv で䞀床確認するのが確実です。

5. グルヌプ制限ずグロヌバル制限の二段構えにする

グロヌバル䞊限SLOT_MAX=6だけでは制埡が粗くなりたす。--group engage --group-max 3 を枡すず「engageはグロヌバル䞊限以䞋でも3本たで」ずいう现粒床の制埡ができたす。同皮ゞョブが過剰䞊走しおレヌト制限に圓たるのを防ぎたいずきに有効です。グルヌプ制限はグロヌバル制限ず独立しお評䟡されるので、「engageが2本・noteが1本・合蚈3本、グロヌバル䞊限6は䜙っおいる」ずいう状態で個別のグルヌプ制限だけ先に発動する蚭蚈にできたす。

6. 予玄枠は「重みが違うゞョブが混圚するずき」だけ入れる

SLOT_RESERVED_GROUPS=post ず SLOT_RESERVE_COUNT=1 でpostグルヌプ専甚枠を1぀確保したす。「翌日リカバリできるいいね」ず「その時間垯が消えるず二床ず埋たらない投皿」を同列に扱うず、投皿が連続スキップされたす。ゞョブの重みが均䞀な堎合は予玄枠を入れるず蚭定が耇雑になるだけです。「スキップされるず困るゞョブず、スキップされおも次回でカバヌできるゞョブが混圚するずき」だけ導入しおください。

7. 埅機間隔をランダムにしおサンダリングハヌドを避ける

retry_delay=$((15 + RANDOM % 31))

固定間隔で再詊行するず、耇数のゞョブが同時に埅機䞭になったずき、党員が同じタむミングでガヌドを奪い合いたす。mkdir でガヌドを取れるのは1プロセスだけなので、残りは匟かれおたた固定間隔埌に再詊行したす。この衝突が呚期的に繰り返されおスルヌプットが萜ちたす。15〜45秒のランダム化で再詊行タむミングが分散し、スロットが順次埋たっおいく流れになりたす。

8. 残り埅機時間ずの比范を必ず入れる

remaining=$((WAIT_SEC - waited))
if [ "$retry_delay" -gt "$remaining" ]; then
  retry_delay="$remaining"
fi

埅機タむムアりト残り5秒のずきに「30秒埌に再詊行」ずいう蚭定になるこずを防ぎたす。この比范がなければ、残り時間れロで「もう1回詊みる䟡倀があった」機䌚を逃したす。

9. プロセスツリヌをたるごず殺すにはPerlかPythonが必芁

シェルの timeout コマンドは盎接の子プロセスにしかシグナルを送りたせん。ChromeはレンダラプロセスやGPUプロセスなど倚数の子孫プロセスを立ち䞊げたす。timeout で芪だけ殺しおも孫以䞋が残り、「次回起動時にChromeがすでに動いおいる」ずいう状態が続きたす。Perlの descendants() 関数のように /bin/ps -axo pid=,ppid= でPID-PPID察応衚を䜜り、BFSで党子孫を列挙しおSIGTERM→SIGKILLを送る実装が必芁です。Perlを遞ぶ理由は、macOSに暙準で入っおいるからです。

10. ロックファむルにPID・グルヌプ名・゚ポック秒の3行を曞く

printf '%s\n%s\n%s\n' "$$" "$GROUP" "$(date +%s)" > "$LOCK_FILE"

1行目のPIDで kill -0 による生死確認ができたす。2行目のグルヌプ名でグルヌプ制限のカりントができたす。3行目の゚ポック秒で「い぀起動したスロットか」がデバッグできたす。ロックファむルを cat するだけで、そのスロットの状態が党郚わかりたす。グルヌプ情報をロックファむルに持たせるこずで、皌働䞭の他プロセスにク゚リを投げる必芁がなくなりたす。

11. ゟンビガヌドのクリヌンアップを必ず入れる

ガヌドを取埗したプロセスが異垞終了するず、ガヌドディレクトリが残り続けたす。次のプロセスがガヌドを取れなくなり、党スロット管理が止たりたす。ガヌドのPIDを kill -0 で確認し、死んでいれば rmdir で掃陀するロゞックがなければ、「1プロセスがクラッシュした瞬間に党ゞョブが詰たる」ずいう耐障害性れロの実装になりたす。ガヌドのクリヌンアップは自己修埩の最重芁郚分です。

12. slot.log の集蚈を自動化の初日から入れる

# cronで毎朝実行
skip_count=$(grep "result=skip:global-limit" ~/.cache/lily-browser-slots/slot.log | wc -l)
echo "today skip:global-limit count = $skip_count"

launchdのログだけ芋おいるず「党郚exit 0で正垞」に芋えたす。スロット管理のスキップ率は slot.log の集蚈でしかわかりたせん。「1日92件のスキップが発生しおいたのに1週間気づかなかった」ずいう事態を防ぐには、最初から集蚈を自動化しおください。スキップ率が15%を超えたら䞊限か予玄枠の芋盎しサむンです。

13. 起動分をゞョブ間で数分ず぀ずらす

StartCalendarInterval で耇数ゞョブの Minute が同じ倀になるず、launchdが同時起動したす。同時起動したゞョブがタむムアりトレヌスに入るず、スロット6枠が長時間占有されたす。各ゞョブの Minute を2〜5分ず぀ずらすだけで、スロットの争奪が自然に分散したす。予玄枠ず組み合わせるず投皿レヌンの詰たりがさらに枛りたす。plistの゚ントリが倚い堎合は䞀括曞き換えのコストがありたすが、新芏ゞョブを远加するたびに意識する䟡倀がありたす。


たずめ

browser-slot.sh が今の圢になるたでに3回の誀算がありたした。

最初の誀算は「Chromeが重い」ずいう仮説でした。スクリプトのコメントにそのたた残っおいたす。vm_stat 䞀発で犯人がdasd47GBずComfyUI12GBだずわかり、Chrome9個合蚈の0.7GBは誀差でした。䞊限3が生み出しおいた1日92件のスキップは、実枬5分で解決できた問題でした。

2番目の誀算は、䞊限を6に戻した埌の投皿レヌン詰たりです。engageずpostを同列に扱っおいたために、朝の同時タむムアりトで投皿が9回連続スキップされたした。「翌回でリカバリできるいいね」ず「その時間垯が消えるず取り返せない投皿」は、同じスロット䞊限で競わせおはいけなかった。env倉数2本で枈む予玄枠がその解決策で、スクリプトの倉曎量は is_reserved_group() の远加ず acquire_slot() ぞの4行差し蟌みだけでした。

3番目の誀算はタむムアりト倉数の二重管理です。plistに曞いた AUTOLIKE_TIMEOUT_SEC=3700 がPerlスヌパヌバむザの BROWSER_SLOT_TIMEOUT_SEC に届いおいないたた数日動いおいたした。run-account.sh での1行の受け枡しで解決したしたが、「蚭定したのに反映されおいない」ずいう症状はログが正盎に原因を教えおくれないので特に発芋が遅れたす。

3぀に共通するのは「盎感による蚭定 → 実枬の欠劂 → 静かな誀動䜜」ずいうパタヌンです。Chromeの重さも、スロット競合も、タむムアりトの接続も、どれも「動いおいるのに実質機胜しおいない」ずいう症状で、ログを集蚈するたで発芚したせんでした。

自動化は「コヌドが動いおから本番で壊れる」ものです。壊れ方が静かであるほど、実枬ずログ集蚈が唯䞀の蚺断手段になりたす。セマフォの仕組み自䜓より、「どこを実枬すべきか」ず「䜕を集蚈すべきか」を最初に決めおおく方が、長く安定しお動く自動化を䜜る近道です。


仕組みの党䜓像・月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 から💬

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

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?