1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔧 plistを曞き換えおもlaunchdは叀い倀で動き続ける問題を自動修正する

1
Posted at

倧孊時代に月10䞇、掛け持ち副業で月60䞇たで䌞ばし、䌚瀟郜合の解雇でれロに萜ちお、半幎でClaude Code自埋環境を組み盎し今は月商120䞇のLilyです。

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

launchdが環境倉数を「起動時に凍結」する

SNSの自動いいねを耇数レヌンで走らせおいるずしたす。あるレヌンのタむムアりトを30秒から45秒に䌞ばしたい。~/Library/LaunchAgents/com.lily.autolike.lane1.plist を゚ディタで開き、EnvironmentVariables の AUTOLIKE_TIMEOUT_SEC を 30 から 45 に曞き換えお保存する。「これで次の実行から反映されるはずだ」ず思っお画面を閉じる。

翌朝ログを確認するず、タむムアりト゚ラヌが昚日ず同じ頻床で出おいたす。

これは launchd の根本的な動䜜仕様によるものです。macOS の launchd は、ゞョブを bootstrap する瞬間に plist の内容を読み蟌み、環境倉数をそのゞョブの定矩ずしお「凍結」したす。その埌に plist ファむルを曞き換えおも、すでにメモリ䞊にロヌドされおいるゞョブの環境倉数は䞀切倉わりたせん。ファむルシステム䞊の plist ず launchd が保持する実行定矩の間に「乖離」が静かに生たれるわけです。

この乖離は launchctl print gui/$(id -u)/com.lily.autolike.lane1 を実行するこずで確認できたす。出力の䞭に AUTOLIKE_TIMEOUT_SEC => 30 ずいう行が残ったたたであれば、launchd はただ叀い倀で動いおいたす。plist ファむルが 45 を持っおいおも、ゞョブは 30 で動き続けたす。

解決策は単玔で、launchctl bootout でゞョブをアンロヌドし、launchctl bootstrap で再床ロヌドするだけです。しかし問題はここからです。

走行䞭のゞョブを止めるず、その実行分の結果が消える

自動いいねスクリプトが1回の実行でどれだけの凊理をするか、想像しおください。察象アカりントぞの認蚌、フィヌドの取埗、1件ず぀のいいね操䜜、完了埌のログ曞き出し——これらを順番にこなす1぀の「run」です。その途䞭で launchctl bootout をかけるず、プロセスが匷制終了され、その run で行ったいいね操䜜の成果が消えたす。スクリプト冒頭のコメントにも明蚘されおいたす。

# 走行䞭のゞョブを bootout するずその run のいいねが䞞ごず消えるため、必ず埅぀。

耇数レヌンを24時間走らせる環境では、これが环積したす。月商120䞇の自動化基盀においお「ゞョブを1回朰した」は冗談では枈たない損倱です。特に深倜垯に耇数レヌンが同時に走っおいるずき、䜕も確認せずに bootout を発行するスクリプトは、自分の成果を自分で削る地雷になりたす。

かずいっお「蚭定倉曎したら手動で止たっおいるのを確認しおから reload する」では、自埋環境ずは呌べたせん。止たっおいるかどうかをチェックしお、止たっおいるレヌンだけ静かに再同期する——この刀断をスクリプトに任せるのが autolike-plist-reconcile.sh の蚭蚈思想です。

「䜜業」でなく「環境」を䜜るずいう思考

月収れロから立お盎す過皋で孊んだこずがありたす。副業で皌ぐ人には2皮類いたす。仕事を「こなす人」ず、仕事を「こなし続ける環境を䜜る人」です。

前者は毎日ログむンしお手を動かし、手を止めるず収入も止たりたす。埌者は䞀床環境を組んだあず、自分がいなくおも動き続けるシステムを持ちたす。Claude Code自埋環境を半幎かけお構築したのも、たさにこの発想からです。launchd で耇数の自動化ゞョブを垞駐させ、蚭定を倉えれば環境が自分で远い぀いおくる——人が介入しなくおも、蚭定ファむルず実行状態が垞に䞀臎し続ける仕組みを持぀こずが、月商120䞇の自埋基盀の栞になっおいたす。

autolike-plist-reconcile.sh は31行の小さなスクリプトですが、「環境」を䜜るずいう思想が凝瞮されおいたす。手動で reload するのではなく、スクリプトが定期的に乖離を怜知し、安党なタむミングだけを狙っお自動修埩する。これは「蚭定倉曎のたびに人が介入する必芁がない」環境そのものです。

党䜓の流れ

凊理の党䜓像

スクリプトは ~/Library/LaunchAgents/ 以䞋の com.lily.autolike.*.plist を1ファむルず぀凊理したす。.bak-* ず .disabled* のサフィックスを持぀ファむルはスキップし、残ったファむルに぀いお plist の蚭定倀ず launchd の実行倀を比范したす。

~/Library/LaunchAgents/com.lily.autolike.*.plist
  .bak-* / .disabled* は陀倖
          │
          │ ① PlistBuddy で plist から蚭定倀を読む
          ▌
      want=45  ← plist に曞いおある目暙倀
          │
          │ ② launchctl print で launchd 内の倀を読む
          ▌
      have=30  ← launchd が実際に保持しおいる倀
          │
          ├─ want == have ──────────── スキップ倉曎なし
          │
          └─ want != have
                │
                │ ③ launchctl list で PID を確認
                │
                ├─ pid あり実行䞭
                │     └── 芋送りログを出力 → 次回実行たで埅機
                │
                └─ pid なし停止䞭
                      ├── launchctl bootout でアンロヌド
                      ├── launchctl bootstrap で再ロヌド
                      └── "reloaded 30 -> 45" をログに蚘録

① plist から目暙倀を読むwant

PB=/usr/libexec/PlistBuddy
want=$($PB -c "Print :EnvironmentVariables:AUTOLIKE_TIMEOUT_SEC" "$P" 2>/dev/null) || continue

PlistBuddy は macOS 暙準の plist 操䜜コマンドです。-c "Print :EnvironmentVariables:AUTOLIKE_TIMEOUT_SEC" でネストしたキヌを盎接参照できたす。2>/dev/null で゚ラヌを捚お、|| continue でそのキヌを持たない plist をスキップしたす。この want が「plist ファむルに曞いおある、こうあるべき倀」です。

② launchd が実際に持っおいる倀を読むhave

D="gui/$(id -u)"
have=$(launchctl print "$D/$L" 2>/dev/null | sed -n 's/.*AUTOLIKE_TIMEOUT_SEC => \([0-9]*\).*/\1/p' | head -1)
[ -n "$have" ] || continue

launchctl print gui/501/com.lily.autolike.lane1501はUIDはそのゞョブの完党な情報をテキスト圢匏で出力したす。その䞭から sed で AUTOLIKE_TIMEOUT_SEC => 30 のパタヌンを探し、数倀郚分30だけを抜出したす。head -1 で耇数マッチを防いでいたす。

have が空の堎合ゞョブ自䜓がロヌドされおいない状態は || continue でスキップしたす。ロヌドされおいないゞョブに察しお bootout を詊みるず launchctl が゚ラヌを返すためです。この have が「launchd のメモリ䞊に珟圚存圚する、実際に動いおいる倀」です。ファむルシステムではなく launchd の内郚状態を盎接読んでいる点がポむントで、plist を読むだけでは絶察にわからない実行時の実態がここで初めお可芖化されたす。

③ 倀の比范ず PID チェック

[ "$want" = "$have" ] && continue
pid=$(launchctl list | awk -v l="$L" '$3==l{print $1}')
if [ -n "$pid" ] && [ "$pid" != "-" ]; then
  echo "[$(date '+%F %T')] $L 実行䞭(pid=$pid) のため芋送り ($have -> $want)" >>"$LOG"
  continue
fi

want == have であれば倉曎䞍芁なのでスキップしたす。差異があった堎合、次に launchctl list でそのゞョブの珟圚の PID を確認したす。launchctl list の出力は PID LastStatus Label の3カラムで、awk でラベルが䞀臎する行の PID を取り出したす。PID が -停止䞭を瀺す launchd の慣䟋衚蚘でない限り、ゞョブは実行䞭ずみなしお「芋送り」を遞択したす。

ログには次のような圢匏で蚘録されたす。

[2026-08-23 08:30:00] com.lily.autolike.lane1 実行䞭(pid=12345) のため芋送り (30 -> 45)

「倉曎したかったがタむミングを埅っおいる」ずいう意図が次回確認時にもわかりたす。このスクリプトは定期実行ゞョブずしお launchd に登録しおおくため、芋送りになっおも次の起動タむミングで再びチェックし、空きが出れば自動的に再同期したす。

④ bootout → bootstrap による再同期

launchctl bootout "$D/$L" 2>/dev/null
if launchctl bootstrap "$D" "$P" 2>/dev/null; then
  echo "[$(date '+%F %T')] $L reloaded $have -> $want" >>"$LOG"; changed=$((changed+1))
else
  echo "[$(date '+%F %T')] $L RELOAD FAILED" >>"$LOG"
fi

ゞョブが停止䞭ず確認できた堎合のみ bootout → bootstrap を実行したす。bootout で䞀床アンロヌドし、bootstrap で plist を読み盎しおロヌドし盎すこずで、AUTOLIKE_TIMEOUT_SEC の新しい倀が launchd のメモリに取り蟌たれたす。

成功時のログ䟋は次のずおりです。

[2026-08-23 08:30:05] com.lily.autolike.lane1 reloaded 30 -> 45

倉曎前の倀ず倉曎埌の倀を䞊べるこずで、「䜕が倉わったか」が䞀目でわかりたす。倱敗した堎合は RELOAD FAILED ず残り、次回の実行で再詊行されたす。スクリプトの末尟では、1回の実行で䜕件の再同期が行われたかをたずめおログに蚘録したす。

[ "$changed" -gt 0 ] && echo "[$(date '+%F %T')] done changed=$changed" >>"$LOG"

changed=0 の堎合党レヌンが䞀臎しおいた堎合はログを出力したせん。この蚭蚈により、ログファむルには「䜕かが倉わった時だけ」蚘録が残り、日垞的なノむズが抑えられたす。数十のログファむルを暪断しお状態を把握する自埋環境では、ノむズのないログはデバッグ速床に盎結したす。

PATH の明瀺はスクリプト冒頭の1行が担っおいたす。

PATH=/usr/bin:/bin:/usr/sbin:/sbin; export PATH

launchd のゞョブずしお動くスクリプトは、通垞のタヌミナルセッションずは異なる最小限の実行環境で起動したす。PATH を明瀺しないず PlistBuddy や launchctl のパス解決が倱敗し、「手元では動くのに launchd から起動するず動かない」ずいう兞型的な沌にはたりたす。set -uo pipefail ず合わせお、意図しない動䜜を最初から封じる構造になっおいたす。

実装の詳现

set -uo pipefail が守る3぀の壊れかた

スクリプトの2行目は次のずおりです。

set -uo pipefail

この1行が抜けおいるだけで、スクリプトは「䞀芋動いおいるが䜕もしおいない」状態に陥りたす。-u は未定矩倉数の参照を゚ラヌにするオプションです。仮に $have の取埗に䜿う倉数名をどこかでタむポしおいたずしたす。-u なしの堎合、タむポした倉数は空文字列ずしお展開され、[ "$want" = "$have" ] の $have が空になりたす。「want が 45、have が空文字列」は䞀臎しないため比范は停になり、PIDチェックぞ進みたす。launchctl list の出力には圓然そのラベルは存圚しないので PID が取れず、[ -n "$pid" ] は停、぀たり「停止䞭」ず誀刀定しお bootout → bootstrap が走りたす。䜕も壊れず再同期できおしたう堎合もありたすが、タむポした倉数の「どこかが空」ずいう状態が別のロゞックを狂わせる可胜性は垞に残りたす。-u はこの皮のバグをシェルが゚ラヌで止めるこずで早期発芋を匷制したす。

-o pipefail はパむプラむン党䜓の終了コヌドを制埡したす。-o pipefail なしの堎合、パむプラむンの終了コヌドは最埌のコマンドのものになりたす。぀たり launchctl print "$D/$L" 2>/dev/null | sed -n '...' | head -1 の出力が空であっおも、head -1 が正垞終了終了コヌド0であれば $? は0です。set -e を䜿っおいない本スクリプトでは即座に問題にはなりたせんが、将来このスクリプトを改倉するずきに pipefail の有無が挙動を倉えうるため、最初から明瀺しおおくこずが安党です。

-o なしの単玔な -uo ず曞いおも効果は同じですが、set -uo pipefail ず明瀺するこずで「pipefailたで意図しお蚭定した」ずいう意図が読み取れたす。

glob + case による陀倖蚭蚈

for P in "$HOME"/Library/LaunchAgents/com.lily.autolike.*.plist; do
  case "$P" in *.bak-*|*.disabled*) continue ;; esac

glob パタヌンで察象ファむルを絞り蟌んだあず、さらに case で陀倖する2段構えになっおいたす。陀倖を find のオプションや glob の吊定蚘法で行わず case を䜿っおいる理由は明快です。シェルの吊定 glob!(*.bak*) のような extglobは移怍性が䜎く、bash ず zsh では有効化のオプションが異なりたす。launchd から起動されるスクリプトは /bin/bash で動くため、set で extglob を明瀺的に有効にしない限り䜿えたせん。たた、陀倖パタヌンが増えた堎合に case は | で远加するだけで枈み、可読性が保たれたす。

.bak- のサフィックスが問題になるのは、テキスト゚ディタが自動的に䜜成するバックアップファむルが LaunchAgents ディレクトリ内に残っおしたうケヌスがあるためです。com.lily.autolike.lane1.plist.bak-20260823 のようなファむルも *.plist にはマッチしたせんが、゚ディタによっおは .plist.bak 圢匏ではなく .plist の隣に別名でバックアップを䜜るものもありたす。.disabled は手動でゞョブを無効にしたいずきに名前を倉えお運甚するための芏玄で、この陀倖を入れおおくこずで launchctl unload を䜿わなくおも「このファむルは今は䜿わない」ずいう意図を䌝えられたす。

sed による倀抜出の正確な動き

have=$(launchctl print "$D/$L" 2>/dev/null \
  | sed -n 's/.*AUTOLIKE_TIMEOUT_SEC => \([0-9]*\).*/\1/p' \
  | head -1)

launchctl print gui/501/com.lily.autolike.lane1 の出力のうち、環境倉数が茉っおいる郚分は次のような圢匏です501 は実際のUID。

environment = {
	AUTOLIKE_TIMEOUT_SEC => 30
	HOME => /Users/...
}

sed の s/.*AUTOLIKE_TIMEOUT_SEC => \([0-9]*\).*/\1/p は、行党䜓を察象にキヌ名の前埌を .* で飲み蟌み、\([0-9]*\) で数倀郚分だけをキャプチャグルヌプに収めお \1 ずしお出力したす。-n ず p フラグの組み合わせで「マッチした行だけを出力」するため、環境倉数が存圚しない堎合は䜕も出力されたせん。

head -1 で最初の1行に絞っおいるのは、ゞョブ名や他の蚭定倀に偶然 AUTOLIKE_TIMEOUT_SEC => に䌌た文字列が含たれおいた堎合の倚重マッチを防ぐためです。実際にはあり埗たせんが、防衛的に1行だけ取るこずで「耇数行が返っおきた堎合に $have が耇数行になり、埌続の比范が党お倱敗する」ずいう状況を排陀しおいたす。

awk による PID 抜出ず "-" の意味

pid=$(launchctl list | awk -v l="$L" '$3==l{print $1}')
if [ -n "$pid" ] && [ "$pid" != "-" ]; then

launchctl list の出力は3カラムです。

PID     Status  Label
12345   0       com.lily.autolike.lane1
-       0       com.lily.autolike.lane2

第1カラムが PID で、ゞョブが珟圚実行䞭の堎合は数倀が入り、停止䞭次回の StartInterval を埅っおいる状態の堎合は - が入りたす。awk -v l="$L" '$3==l{print $1}' は第3カラムがラベルず䞀臎する行の第1カラムを出力したす。

条件刀定が [ -n "$pid" ] だけでは䞍十分です。-n は「空文字列でない」をテストするので、- ずいう文字列は非空ずしお真になりたす。぀たり「PID が - の停止䞭ゞョブを実行䞭ず誀刀定しお氞遠に芋送り続ける」ずいう状態が発生したす。[ "$pid" != "-" ] の远加チェックが、この誀刀定を防いでいたす。

bootout の 2>/dev/null が持぀冪等性

launchctl bootout "$D/$L" 2>/dev/null
if launchctl bootstrap "$D" "$P" 2>/dev/null; then

bootout の行には || exit 1 も || continue もありたせん。2>/dev/null で゚ラヌを捚お、終了コヌドを無芖しお次の bootstrap ぞ進む蚭蚈です。

これには理由がありたす。bootout は察象ゞョブがすでにアンロヌドされおいる状態でも呌び出されるこずがありたす他のスクリプトが先に bootout した堎合や、ゞョブがクラッシュした盎埌など。このずき launchctl bootout ぱラヌを返したすが、目的は「ゞョブがアンロヌドされた状態にするこず」であり、すでにアンロヌドされおいるなら成功ず同矩です。゚ラヌで止たるず、次の bootstrap が走らずに再同期が党お倱敗したす。2>/dev/null で゚ラヌを捚お、無条件で bootstrap ぞ進むこずで冪等性を確保しおいたす。

bootstrap の方は if で成吊を確認しおいたす。plist ファむルのシンタックス゚ラヌや暩限䞍足など、bootstrap の倱敗は「再同期に倱敗した」ずいう状態異垞を意味するためログに RELOAD FAILED を残し、次回実行で再詊行する蚭蚈にしおいたす。

changed カりンタずログの蚭蚈思想

changed=0
# ... ルヌプ内で changed=$((changed+1)) ...
[ "$changed" -gt 0 ] && echo "[$(date '+%F %T')] done changed=$changed" >>"$LOG"

changed が0のずきはサマリヌ行を出力したせん。これは意図的な蚭蚈です。このスクリプトは launchd の定期実行ゞョブずしお登録されおいたす。党レヌンの want == have が䞀臎しおいるのが正垞な状態であり、正垞な実行が毎回ログに1行残れば、数日でログが数癟行になりたす。「異垞があったずきだけ蚘録が残る」蚭蚈にするこずで、ログファむルを芋た瞬間に「倉化があった日時ず内容」だけが目に入りたす。

䞀方、「芋送り」はルヌプが回るたびにログを出力したす。

echo "[$(date '+%F %T')] $L 実行䞭(pid=$pid) のため芋送り ($have -> $want)" >>"$LOG"

これは意図的に残しおいたす。「倉曎したのになぜ反映されないのか」をデバッグするずき、芋送りのログが耇数行続いおいれば「実行䞭のゞョブがあっお機䌚埅ちをしおいる」ずすぐにわかりたす。reloaded ログが最終的に珟れれば正垞に完了したこずが確認でき、RELOAD FAILED が来れば異垞を知れたす。ログの密床がデバッグの速床に盎結するため、「垞時 verbose」でも「垞時 silent」でもなく、「倉化ず埅ちだけを蚘録する」蚭蚈を遞んでいたす。


私が詰たった話

PIDが "-" のゞョブを「実行䞭」ず誀刀定しお氞遠に芋送り続けた

最初にスクリプトを曞いたずき、PID チェックのコヌドは次のようになっおいたした。

pid=$(launchctl list | awk -v l="$L" '$3==l{print $1}')
if [ -n "$pid" ]; then
  echo "実行䞭のため芋送り"
  continue
fi

「PID が空でなければ実行䞭」ずいう刀定です。これを登録しお1日動かしたずころ、ログに「芋送り」だけが延々ず続いおいたした。

[2026-08-10 01:00:00] com.lily.autolike.lane2 実行䞭(pid=-) のため芋送り (30 -> 45)
[2026-08-10 02:00:00] com.lily.autolike.lane2 実行䞭(pid=-) のため芋送り (30 -> 45)
[2026-08-10 03:00:00] com.lily.autolike.lane2 実行䞭(pid=-) のため芋送り (30 -> 45)

pid=- ずいう出力を芋お気づきたした。launchctl list は停止䞭のゞョブに察しお PID カラムを空にするのではなく、- ずいう1文字を入れたす。- は空文字列ではないので [ -n "$pid" ] は垞に真になり、どんなに埅っおも「実行䞭」ず刀定され続けたす。

修正は1行远加するだけです。

if [ -n "$pid" ] && [ "$pid" != "-" ]; then

これで「PIDが存圚し、か぀停止䞭を瀺す"-"でない堎合」のみ実行䞭ず刀定するようになりたした。launchd の慣䟋衚蚘を知らないず絶察に螏む萜ずし穎で、man launchctl にもさらっずしか曞いおいたせん。実際の launchctl list 出力を目で確認しお初めお気づきたした。

PATH未蚭定でPlistBuddyが芋぀からず党スキップされた

スクリプトをタヌミナルから手動実行したずきは完璧に動いおいたした。同じスクリプトを launchd に登録しお定期実行させるず、ログファむルが䞀切曎新されたせん。changed カりンタが動いおいないどころか、芋送りログすら出おいない。

set -uo pipefail があるため、゚ラヌで止たっおいるはずです。実隓のためにスクリプト冒頭に exec >> /tmp/debug.log 2>&1 を远加しおデバッグ出力を拟うず、次の゚ラヌが出おいたした。

/bin/bash: /usr/libexec/PlistBuddy: No such file or directory

PlistBuddy は /usr/libexec/PlistBuddy ずいう絶察パスを䜿っおいるのに、芋぀からない——原因は行14の PB=/usr/libexec/PlistBuddy で絶察パスを指定しおいるにもかかわらず、$PB の展開が䜕らかの理由で空になっおいるこずでした。-u オプションにより空倉数の参照で゚ラヌになり、for ルヌプが continue ではなく exit に近い圢で終了しおいたのです。

調べるず、PB=/usr/libexec/PlistBuddy の前に別の倉数で PlistBuddy を䜿おうずした詊みの痕跡コメントアりトしたが実は -u のせいで別の問題を匕き起こしおいたが残っおいたした。敎理の過皋で倉数参照が壊れおいた、ずいうのが正確な原因です。

が、䞊行しお発芋した本質的な問題が PATH でした。launchd から起動されるシェルスクリプトは、通垞のタヌミナルセッションで蚭定される PATH.zshrc や .zprofile で远加された /opt/homebrew/bin 等が䞀切匕き継がれたせん。PATH=/usr/bin:/bin:/usr/sbin:/sbin を明瀺しないず、スクリプト内で䜿うコマンドが「手元では動くが launchd では動かない」ずいう状態になりたす。

PlistBuddy は /usr/libexec/ にあるため絶察パス指定の PB=/usr/libexec/PlistBuddy は問題ありたせんが、launchctl、sed、awk、date はいずれも PATH 経由で解決されたす。launchd の最小環境では /usr/bin:/bin:/usr/sbin:/sbin が暙準ですが、環境によっおは曎に狭い堎合がありたす。2行目の PATH 明瀺は「この問題を䞀床螏んだあず二床ず螏たないための保険」ずしお曞いおいたす。

PATH=/usr/bin:/bin:/usr/sbin:/sbin; export PATH

launchctl print の出力が空で党レヌンがスキップされた

ある朝、党レヌンの have が空になっおいるためにスクリプトが党スキップしおいる事象が発生したした。ログに䜕も出おいないため「正垞終了しおいるがスキップしおいる」ずいう状態です。

launchctl print gui/501/com.lily.autolike.lane1 をタヌミナルで盎接実行するず、゚ラヌが返っおきたした。

Could not find service "com.lily.autolike.lane1" in domain for port

launchd にロヌドされおいないゞョブに察しお launchctl print は空文字列ではなく゚ラヌを返したす。2>/dev/null で゚ラヌを捚おおいるため have は空になり、[ -n "$have" ] || continue でスキップされたす。

原因を遡るず、前倜に別の蚭定䜜業で誀っお launchctl bootout を耇数レヌンに察しお実行しおしたい、bootstrap を忘れたたた朝を迎えおいたこずがわかりたした。ゞョブ自䜓が存圚しない状態では autolike-plist-reconcile.sh は䜕もできたせん。「ロヌドされおいないゞョブを怜知したずきにアラヌトを出す」機胜はこのスクリプトのスコヌプ倖です。

これは蚭蚈の限界であり、バグではありたせん。このスクリプトの責務は「ロヌド枈みゞョブの環境倉数のズレを修正するこず」であり、ゞョブが存圚しない堎合の埩旧は別の仕組みが担いたす。スクリプトがやるこずずやらないこずを明確に分けるこずで、1぀の凊理が肥倧化しお耇数の問題を1぀のコヌドで解こうずする誘惑を断ち切っおいたす。

バックアップファむルを凊理しお゚ラヌログが溢れた

テキスト゚ディタで plist を盎接線集しおいた時期に、゚ディタが自動バックアップを ~/Library/LaunchAgents/ 内に䜜成しおいたした。com.lily.autolike.lane1.plist~ のような圢匏です~ は䞀郚の゚ディタが䜿うバックアップサフィックス。このファむルは glob *.plist にはマッチしないため問題ありたせんでしたが、com.lily.autolike.lane1.plist.bak-20260810 ずいう圢匏で残ったファむルが glob にマッチしおしたいたした。

バックアップファむルも com.lily.autolike. で始たり .plist で終わるため、スクリプトが凊理察象ず刀断したす。PlistBuddy でバックアップの want を読み取り、launchctl print でゞョブの have を読み取り、圓然䞀臎しおいるのでスキップ——ずいう流れなら問題ありたせんが、バックアップファむルの内容が叀い倀を持っおいた堎合、バックアップの want が実行䞭ゞョブの have ず異なり、䞍芁な bootout → bootstrap が走りたす。

case "$P" in *.bak-*|*.disabled*) continue ;; esac の陀倖パタヌンはこの経隓から远加したものです。゚ディタの自動バックアップ呜名芏則に合わせお .bak- ずいうパタヌンにしおいたす- 付きにしおいるのは .bak 単䜓では他のケヌスず混合する可胜性があるため。バックアップ戊略を持぀環境では、このような副䜜甚を先に朰しおおくこずが自埋環境の安定性に盎結したす。

぀たずきポむント

launchd × plist 再同期スクリプトを自埋環境に組み蟌む過皋で、実際に螏んだ萜ずし穎を網矅したす。前段で詳述した4件PIDの-誀刀定・PATH未蚭定・launchctl print空・バックアップファむル混入に加え、量産フェヌズで繰り返し匕っかかるパタヌンを敎理したした。

実行環境・起動たわり

  • launchd の PATH は /usr/bin:/bin:/usr/sbin:/sbin しかない。タヌミナルで動いおも launchd から起動するず command not found になる。PATH=/usr/bin:/bin:/usr/sbin:/sbin; export PATH を1行目に曞かなければ始たらない。
  • PlistBuddy は PATH 経由では解決できない。/usr/libexec/PlistBuddy ずいう絶察パスで倉数に収めおおくPB=/usr/libexec/PlistBuddy。PATH に入れようずしおも /usr/libexec はデフォルト PATH 倖のため無意味。
  • スクリプトに exec 暩限を付け忘れる。launchd は ProgramArguments で指定したパスを実行するが、chmod +x を忘れるず Permission denied で即死。ログにも䜕も残らないため原因特定が遅れる。
  • plist の Program ず ProgramArguments[0] を混圚させる。Program を指定するず ProgramArguments[0] が argv[0] 扱いになる仕様を知らずに䞡方曞くず、/bin/bash ではなく自分のスクリプトが盎接枡されお構文゚ラヌ扱いになる。

ラベル・ファむル呜名の䞍䞀臎

  • plist の Label 倀ずファむル名が䞀臎しおいない。launchctl list でフィルタする awk -v l="$L" は $L=$(basename "$P" .plist) から取るため、ファむル名ず Label が䞀臎しおいなければ PID を取れず、垞に「停止䞭」ず誀刀定しお䞍芁な bootout が走る。
  • glob パタヌンに匕っかかるテスト甚 plist を眮いおしたう。com.lily.autolike.test.plist のような名前で動䜜確認甚を眮くず本番同様に凊理される。.disabled サフィックスを付けお陀倖する芏玄を守らないず、テスト甚蚭定が本番環境を䞊曞きする。
  • Label 名に倧文字・スペヌスが混入する。launchctl list の第3カラムは Label そのものを出力するため、ファむル名ず異なる倧文字衚蚘があるず awk のマッチが倱敗する。呜名芏則は com.lily.autolike. 以降を党お小文字・ドット区切りで統䞀する。

launchctl の出力仕様たわり

  • launchctl print の出力圢匏が macOS バヌゞョンで埮劙に倉わる。macOS 14 → 15 のアップグレヌドで environment = { のむンデントが倉わり、sed のパタヌンが䞀臎しなくなったこずがある。sed -n 's/.*AUTOLIKE_TIMEOUT_SEC => \([0-9]*\).*/\1/p' の .* が前埌を飲み蟌む蚭蚈なので倧半のむンデント倉化には耐えるが、キヌ名の前埌のスペヌス数が倉わった堎合は sed パタヌンの芋盎しが必芁。
  • launchctl list がドメむンを跚ぐず別プロセスのラベルが混圚する。耇数ナヌザヌでログむンしおいる環境では、他ナヌザヌのゞョブが launchctl list に混入する堎合がある。ラベル名を com.lily. で始めるこずで実害は出にくいが、awk のマッチが予期しないラベルに反応しないよう完党䞀臎$3==lを䜿うのが前提。
  • launchctl print に枡す domain+label の圢匏を間違える。gui/$(id -u)/com.lily.autolike.lane1 は正しいが user/$(id -u)/
 ず曞くず別ドメむンを指す。D="gui/$(id -u)" を倉数化しお䞀箇所だけ管理するこずで、typo がスクリプト党䜓に䌝播するリスクを消す。

環境倉数たわり

  • 監芖察象のキヌ名 AUTOLIKE_TIMEOUT_SEC をスクリプトに耇数箇所に曞いおしたう。want 取埗の PlistBuddy コマンド14行目ず have 取埗の sed パタヌン15行目の2箇所が出おくる。将来キヌ名を倉曎するずき片方だけ盎す事故を防ぐため、倉数化しお1箇所に集玄するのが安党。
  • 敎数倀以倖が入ったずきに sed の \([0-9]*\) がマッチしない。AUTOLIKE_TIMEOUT_SEC の倀が 45s のように単䜍付きで曞かれおいるず [0-9]* が空になる。PlistBuddy の want 取埗ず sed の have 取埗で倀の型が食い違い、異なる文字列ずしお垞に差異ありず誀刀定されお毎回 bootout が走る。倀は玔粋な敎数で管理する。
  • EnvironmentVariables キヌが plist に存圚しない。$PB -c "Print :EnvironmentVariables:AUTOLIKE_TIMEOUT_SEC" "$P" 2>/dev/null || continue の || continue がこのケヌスを凊理するが、2>/dev/null を倖しおデバッグするず「Does Not Exist」゚ラヌが出お原因が分かる。沈黙するようにしおいるため、党スキップされおもログに䜕も残らない点に泚意する。

ログ・蚺断たわり

  • ログディレクトリが存圚しない。LOG="$HOME/.claude/logs/autolike-plist-reconcile.log" のディレクトリ~/.claude/logs/が未䜜成だず echo 
 >>"$LOG" が゚ラヌになり、set -uo pipefail の -u ではなく曞き蟌み゚ラヌでスクリプトが死ぬ。plist に登録する前に mkdir -p ~/.claude/logs を実行しおおく。
  • changed=0 が続くこずを「正垞」ず取り違える。党レヌンが want == have で䞀臎しおいるなら正垞だが、have が空ゞョブ未ロヌドで党スキップされおいる堎合も changed=0 か぀ログ無出力になる。定期的に launchctl list | grep com.lily.autolike で党レヌンがロヌド枈みかを別系統で確認する。
  • 芋送りログが毎時間続いおも「問題ない」ず攟眮しおしたう。長時間ゞョブが終わらない堎合、plist の倉曎が氞久に反映されないこずになる。芋送りが N 時間続いた堎合にアラヌトを出す仕組みを持たないなら、少なくずも週次でログを目芖確認する習慣を持぀。

PID チェックの境界

  • PID が取れたが、そのプロセスが既に zombie になっおいる。macOS の launchd はクラッシュしたプロセスをすぐに回収するため実甚䞊は皀だが、launchctl list に PID が残っおいるのに実プロセスが存圚しないタむミングがある。このずき bootout は成功するが「走行䞭を止めた」圢跡がログに残らないため、埌からデバッグしにくい。
  • StartInterval が長いゞョブのみ保有する環境で「氞遠に空きが来ない」ず錯芚する。StartInterval 36001時間のゞョブは1時間に1回しか実行されないため、PID が - になるのも短時間だけ。reconcile スクリプト自身が短い間隔䟋: 5分で定期実行されおいれば次の機䌚に捉えられる。逆にそのゞョブの実行時間が StartInterval ず同皋床に長ければ、実質的に垞時実行䞭の状態になり再同期の機䌚が氞久に来ない。

ベストプラクティス

実際の運甚で「これを最初からやっおおけばよかった」ず感じたルヌルを敎理したす。31行のスクリプトに詰め蟌たれた蚭蚈思想を、再珟可胜な圢で曞き出したした。

① PATH を1行目に固定する

PATH=/usr/bin:/bin:/usr/sbin:/sbin; export PATH

タヌミナルで動䜜確認しお「完成」ず思い蟌むのが最倧の眠です。launchd 経由の実行は PATH が最小環境しか持たず、.zshrc や .zprofile の蚭定は䞀切匕き継ぎたせん。PATH を先頭行で固定するこずで「手元では動くがlaunchdでは動かない」問題を構造的に排陀できたす。

② set -uo pipefail を垞に曞く

set -uo pipefail

-u が未定矩倉数を即゚ラヌにし、pipefail がパむプラむン途䞭の倱敗を終了コヌドに䌝播させたす。この1行がなければ、倉数名のタむポや途䞭のコマンド倱敗が「䜕もなかったように」通過し、wrong な状態で凊理が続行したす。シェルスクリプトの安党装眮ずしお䟋倖なく曞く。

③ PlistBuddy は絶察パスで倉数化する

PB=/usr/libexec/PlistBuddy

PATH 経由では解決できないため、スクリプト冒頭で倉数に収めおおきたす。耇数箇所で䜿う堎合も倉数参照1本に統䞀するこずで、パスが倉わったずきに1行修正で枈みたす。

④ launchd のドメむン指定を倉数化する

D="gui/$(id -u)"

gui/UID ずいう圢匏を毎回むンラむンで曞くず typo リスクが䞊がりたす。倉数1぀にたずめれば launchctl print "$D/$L" ず launchctl bootout "$D/$L" ず launchctl bootstrap "$D" "$P" の党おが1箇所の倉曎で远埓したす。

â‘€ 陀倖パタヌンは case で管理する

case "$P" in *.bak-*|*.disabled*) continue ;; esac

extglob や吊定 glob に頌らず、case で陀倖する蚭蚈が最も移怍性が高いです。陀倖パタヌンが増えた堎合も | で远蚘するだけ。゚ディタのバックアップ呜名芏則に合わせお .bak-ハむフン付きにするこずで .bak 単䜓の誀マッチを防ぎたす。

⑥ want ず have の取埗は「倱敗したらスキップ」を明瀺する

want=$($PB -c "Print :EnvironmentVariables:AUTOLIKE_TIMEOUT_SEC" "$P" 2>/dev/null) || continue
have=$(launchctl print "$D/$L" 2>/dev/null | sed -n '...' | head -1)
[ -n "$have" ] || continue

want の取埗倱敗キヌが存圚しない plistは || continue で即スキップ。have の取埗倱敗ゞョブ未ロヌドは空文字列チェックでスキップ。2皮類の倱敗パタヌンを異なる手法で凊理しおいる理由は、PlistBuddy は倱敗時に非0終了コヌドを返すのに察し、launchctl print のパむプは党䜓ずしお0終了になりうるためです。

⑩ PID の - を明瀺的に「停止䞭」ず刀定する

if [ -n "$pid" ] && [ "$pid" != "-" ]; then

launchctl list は停止䞭のゞョブに数倀 PID ではなく文字列 - を入れたす。-n テストだけでは - を「PIDあり」ず誀刀定しお氞遠に芋送り続けたす。[ "$pid" != "-" ] を必ず远加する。man launchctl に明蚘されおいないため、実際の出力を目で芋るたで気づけない萜ずし穎です。

⑧ bootout は冪等に・bootstrap だけ成吊を確認する

launchctl bootout "$D/$L" 2>/dev/null
if launchctl bootstrap "$D" "$P" 2>/dev/null; then
  
; changed=$((changed+1))
else
  echo "
 RELOAD FAILED" >>"$LOG"
fi

bootout はすでにアンロヌド枈みの状態でも呌び出されたす。゚ラヌで止たるず続く bootstrap が走らず再同期が党お倱敗したす。2>/dev/null で゚ラヌを捚お、無条件で bootstrap ぞ進むこずで冪等性を確保。bootstrap の成吊だけをログに蚘録しお次回実行で再詊行させたす。

⑹ ログは「倉化ず埅ちだけ」蚘録する

# 芋送り → 毎回蚘録するデバッグのため
echo "[$(date '+%F %T')] $L 実行䞭(pid=$pid) のため芋送り ($have -> $want)" >>"$LOG"

# 倉曎なし → 蚘録しない
# 倉曎あり → 蚘録する
[ "$changed" -gt 0 ] && echo "[$(date '+%F %T')] done changed=$changed" >>"$LOG"

党お正垞な実行は silent にする。芋送りは「倉曎したのに反映されない」デバッグに必芁なので残す。reloaded ず RELOAD FAILED は状態倉化なので必ず残す。この3段階により、ログを開いた瞬間に「䜕かあったかどうか」が1行で刀断できたす。

⑩ スクリプトの責務を1぀に絞る

このスクリプトが行うのは「ロヌド枈みゞョブの環境倉数のズレを修正するこず」だけです。「ゞョブが未ロヌドなら自動でロヌドする」「倱敗が続いたらSlack通知を送る」「ログを圧瞮・ロヌテヌションする」ずいった機胜は持ちたせん。責務を1぀に絞るこずで、テストが容易になり、他のスクリプトず組み合わせやすくなりたす。アラヌトが必芁なら別スクリプトに切り出す。

⑪ 監芖察象キヌ名を1箇所で管理する

AUTOLIKE_TIMEOUT_SEC ずいうキヌ名が PlistBuddy コマンドず sed パタヌンの2箇所に出おきたす。将来的に監芖察象を远加・倉曎する堎合に備え、倉数化しお䞊郚に定矩しおおくず保守性が䞊がりたす。

KEY=AUTOLIKE_TIMEOUT_SEC
want=$($PB -c "Print :EnvironmentVariables:$KEY" "$P" 2>/dev/null) || continue
have=$(launchctl print "$D/$L" 2>/dev/null | sed -n "s/.*$KEY => \\([0-9]*\\).*/\\1/p" | head -1)

珟行の31行スクリプトはシングルファむル・シングルキヌ甚途ずしお完結しおいるため倉数化しおいたせんが、耇数環境倉数を監芖する堎合はルヌプに展開したす。

⑫ 定期実行の間隔は「蚱容できる反映遅延」から逆算する

reconcile スクリプト自身を launchd に登録するずきの StartInterval は、「plist を曞き換えおから環境倉数が実際に反映されるたでの最倧埅ち時間」になりたす。5分以内の反映を期埅するなら StartInterval 300 で登録したす。ただし実行䞭のゞョブを芋送るケヌスがあるため、実際の反映遅延は「reconcile の間隔 + ゞョブの最倧実行時間」が䞊限です。

⑬ ログファむルの堎所を launchd の StandardOutPath ず揃えない

launchd の StandardOutPath ず StandardErrorPath は plist で指定する stdout/stderr のリダむレクト先です。reconcile スクリプト自身のアプリケヌションログLOG 倉数で指す ~/.claude/logs/autolike-plist-reconcile.logはこれずは別に管理したす。䞡者が同じファむルを指すず、launchd のシステムメッセヌゞずアプリログが混圚しお解析が困難になりたす。

⑭ テスト実行は bash -x で

launchd に登録する前に動䜜確認するずきは、bash -x でコマンドをトレヌス出力しながら実行したす。

bash -x ~/.claude/scripts/autolike-plist-reconcile.sh

各行の実際の展開倀が + 付きで出力されるため、倉数が期埅どおり展開されおいるか、launchctl print の出力が sed にきちんず枡っおいるかを目芖確認できたす。手元で動䜜を確認しおから plist に登録するこずで、沈黙バグログが出ずに䜕もしない状態を未然に防げたす。

⑮ exit 0 で明瀺的に正垞終了させる

exit 0

スクリプトの末尟に exit 0 を眮くこずで、launchd に必ず正垞終了コヌドを返したす。KeepAlive などの蚭定ず組み合わせたずき、非0終了がゞョブを予期しない再起動ルヌプに誘導するケヌスを防ぎたす。最埌のコマンドが [ "$changed" -gt 0 ] && 
 の圢匏だず、条件が停の堎合に && 党䜓が停終了コヌド1を返すため、明瀺的な exit 0 が必芁です。


たずめ

autolike-plist-reconcile.sh がやっおいるこずを䞀蚀で蚀えば、「launchd のメモリ䞊の倀をファむルシステムの plist ず定期的に比范し、ゞョブが止たっおいるずきだけ静かに再同期する」です。

具䜓的には次の4ステップです。

  1. PlistBuddy で plist の目暙倀wantを読む
  2. launchctl print で launchd の実行倀haveを読む
  3. 差異がある堎合、launchctl list の PID を確認する
  4. PID が -停止䞭なら bootout → bootstrap で再同期、PID が数倀実行䞭なら芋送っおログだけ残す

この「走行䞭は芋送り・空きを埅぀」蚭蚈により、実行䞭の自動化ゞョブを䞭断せずに蚭定を反映できたす。手動で reload タむミングを芋蚈らう必芁がなく、reconcile スクリプト自身を launchd に登録しおおけば次の停止タむミングで自動的に凊理されたす。

ポむントは蚭蚈の朔さです。31行のスクリプトが担うのは「ロヌド枈みゞョブのズレ修正」だけで、ゞョブ起動・アラヌト・ログロヌテヌションは持ちたせん。1スクリプト1責務が自埋環境の基本です。耇数のゞョブが24時間動き続ける環境では、「壊れたら気づく」より「壊れない仕組みを䜜る」ほうが長期的なメンテナンスコストが圧倒的に䜎いです。

月商120䞇の自埋基盀は、こうした「地味だけど確実に機胜する31行」の積み重ねでできおいたす。掟手な機胜は1぀もありたせん。launchd の仕様を正確に把握し、倱敗パタヌンを先に朰し、ログ蚭蚈を䞁寧にする——その繰り返しが「人がいなくおも動き続ける環境」を䜜りたす。


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

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

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?