2026幎8æ23æ¥12æ04åããããã®å¶æ¥DMãæ·±å€2æ31åã»10æ31åã»18æ31åã®1æ¥3åã ãåãèšå®ã«æžãæããããŠãããè¢«å®³ã¯æ°ã¥ããŸã§ã®æ°æ¥éãæ¯æ¥æ°åä»¶ã®æ©äŒæå€±ã ã£ããç¯äººã¯ä»ãç¹å®ã§ããŠããªãã
ãªããã®ä»çµã¿ãå¹ãã®ã
èªååããæ¢ãŸã£ãŠããããšãã«æ°ã¥ããªãåé¡
å人éçºã§çšŒãä»çµã¿ã¯ãçªãè©°ãããšãèªååã®çšŒåçïŒåçããšããçåŒã«è¡ãçããŸããæ10äžã®å€§åŠçã ã£ãããããæãæã¡æ60äžã«ãªããäŒç€Ÿéœåã§è§£éãããŠãŒãã«æ»ããå幎ã§Claude Codeã®èªåŸç°å¢ãçµã¿äžããŠæå120äžãäœããã®ã¯ã人éãå¯ãŠããéãåãç¶ããå¶æ¥DMã®ã¬ãŒã³ããã£ãããã§ãã
å¶æ¥DMã®èªåéä¿¡ã¯4æ¬ã®launchd jobã§æ§æãããŠããŸããInstagramã»Threadsã»ãã©ãã¯ãŒéæã»YouTubeãšããã©ãããã©ãŒã ããšã«ç¬ç«ããplistãšPythonã¹ã¯ãªãããååšããŸãããããã¯StartCalendarIntervalã§èµ·åæå»ãå¶åŸ¡ããŠãããããšãã°com.lily.outreach-igã¯8:20ã»10:20ã»12:20ã»14:20ã»16:20ã»18:20ã»20:20ã»22:20ã®8åãcom.lily.outreach-thã¯9:38ãã21:38ãŸã§2æéããã«7åãæ¥äžã®æé垯ã«éä¿¡ãèµ°ããŸãã
åé¡ã¯ãlaunchdã®plistã¯åãªãXMLãã¡ã€ã«ã§ããã誰ã§ãïŒãããã¯äœã§ãïŒæžãæãããããšããç¹ã§ããmacOSã¯plistã倿ŽãããŠããŠãŒã¶ãŒã«éç¥ããŸãããlaunchctl listã§ãžã§ãã"running"ãšè¡šç€ºãããŠããŠããã¹ã±ãžã¥ãŒã«èªäœã¯æžãæããããåŸã®å€ã§åããŠããå¯èœæ§ããããŸãã8æ23æ¥ã®ä»¶ããŸãã«ããã§ããžã§ãã¯çããŠããããã ãã1æ¥3åã»æ·±å€åž¯ããšããå¥ã®èšå®ã§ã
æ°ã¥ãããã£ããã¯ãããæ¥ã®å€æ¹ã«æåã§DMéä¿¡æ°ã®ãã°ãèŠãããšã§ããã12æå°ãš16æå°ã«ãŒããç¶ããŠããã®ã§èª¿ã¹ããšãcom.lily.outreach-igã®plistå
StartCalendarIntervalã2:31 / 10:31 / 18:31ã®3ãšã³ããªã ãã«ãªã£ãŠããŸãããåæã«com.lily.outreach-thã7åãã3åã«æžããããŠãããmtimeïŒæçµæŽæ°æå»ïŒã¯2026-08-23 12:04ã§ããã
ãã®çµéšããåŠãã ã®ã¯ããåããŠããããšãæ£ããåããŠãããã¯å¥ç©ã ãšããããšã§ããç£èŠãã¹ãã¯ããã»ã¹ã®çæ»ã§ã¯ãªããplistå ã®èšå®å€ãã®ãã®ã§ãã
ãæ»ããã ãã§ãªããèšé²ãããèšèšã®äŸ¡å€
åçŽãªæŽåæ§ãã§ãã¯ãªããcronãWatch Pathsã§å®è£ ããæ¹æ³ããããŸãããããããããä»åãã ãã£ãã®ã¯ã埩å ãšåæã«forensicïŒæ³ç蚌æ ïŒãã°ãæ®ããšããèšèšã§ããã
çç±ã¯äºã€ãããŸããäžã€ç®ã¯åçŸæ§ã®ç¢ºä¿ã§ããåãæžãæããç¹°ãè¿ããããªããã©ã®ããã»ã¹ãçŽåã«èµ°ã£ãŠãããã®ã¹ãããã·ã§ãããããã°ç¯äººåè£ãçµããŸããäºã€ç®ã¯ããªãå€ãã£ãã®ããåŸãã远ãããå®å¿æã§ããèªååã®ç°å¢ã¯è€éã§ãèªåãã©ããã®ã¹ã¯ãªããã§èª€ã£ãŠæžãæããå¯èœæ§ãåŠå®ã§ããŸããããã°ãããã°ãèªåã®ãã¹ãå€éšã®å¹²æžããã®åãåããã§ããŸãã
outreach-schedule-guard.shãæ€ç¥æã«ããããšã¯4ã€ã§ããmtimeä»ãã§æžãæããèšé²ããã»çŽåã®ããã»ã¹äžèŠ§ãæ®ãã»plistãã¿ã€ã ã¹ã¿ã³ãä»ãããã¯ã¢ãããšããŠã³ããŒããã»æ£ããå€ã«æ»ããŠlaunchdã«åèªã¿èŸŒã¿ãããããã®ãã¡forensicã®éšåã¯ã¹ã¯ãªãã50è¡ã®ãã¡ååè¿ããå ããŠããŸãããåãã°ããããšå²ãåã£ãŠåŸ©å
ã ãã«ããããšãã§ããŸããããåå ç©¶æã諊ããããªãã£ãã
launchd plistãæã€æ§é ã®æ¥æ
StartCalendarIntervalã®æ§é ãçè§£ãããšããªãpython3ã®plistlibã§èªã¿åãããšãæåãªã®ããããããŸãã
launchd plistã¯XML圢åŒïŒãŸãã¯plutil -convert binary1ã§ãã€ããªåœ¢åŒïŒã§ä¿åãããŸããcom.shun.self-repair.plistãäŸã«åããšã以äžã®ãããªé
åæ§é ã«ãªã£ãŠããŸãã
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>20</integer>
</dict>
<dict>
<key>Hour</key>
<integer>13</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
<dict>
<key>Hour</key>
<integer>19</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
</array>
ãã®HourãšMinuteãæŽæ°åã§æ ŒçŽãããŠãããšããç¹ãèã§ããã·ã§ã«ã¹ã¯ãªããã§XMLãgrepãawkã§ããŒã¹ããã®ã¯åã®ä¿èšŒããªãããã€ããªplistã«ã¯ãŸã£ããéçšããŸãããpython3 -c "import plistlib" ã§plistãèªãã°ãããã¹ã圢åŒã§ããã€ããªåœ¢åŒã§ãåãAPIã§æ±ããHourã®å€ã¯ç¢ºå®ã«intåãšããŠåãåºããŸãã
æžãæãæ€ç¥ã¯ãã®èªã¿åãå€ãšSPECSé
åã®æåŸ
å€ãæå忝èŒããã ãã§ããæåŸ
å€ã¯"8:20,10:20,12:20,14:20,16:20,18:20,20:20,22:20"ãšãã圢åŒã§çæããå®éã®plistããåãåºããå€ãšäžèŽããªããã°ã¢ã©ãŒããåºããŸãã
å šäœã®æµã
ã¢ãŒããã¯ãã£æŠèŠå³
宿å®è¡ïŒlaunchd jobïŒ
â
âŒ
outreach-schedule-guard.sh
â
ââ SPECSé
åããæåŸ
ã¹ã±ãžã¥ãŒã«ãçæ
â com.lily.outreach-ig â 8,10,12,14,16,18,20,22 æ :20
â com.lily.outreach-th â 9,11,13,15,17,19,21 æ :38
â com.lily.followers-outreach â 8,10,12,14,16,18,20,22 æ :35
â com.lily.outreach-yt â 10,12,14,16,18,20 æ :52
â
ââ python3 plistlib ã§å plist ã® StartCalendarInterval ãå®èªã¿åã
â
ââ [äžèŽ] â åŠçãªãã»çµäº
â
ââ [äžäžèŽ] ââââââââââââââââââââââââââââââââââââââââââ
â
â forensicãã°åºå â
- plist ã® mtime ãèšé² â
- çŸåšå€ vs æåŸ
å€ã䞊ã¹ãŠèšé² â
- ps ã§ python/node/bash/launchctl/plutil â
ã®çŽè¿ããã»ã¹ããã£ããã£ïŒæå€§25ä»¶ïŒ â
â
â¡ ããã¯ã¢ããäœæ â
$label.plist.bak-guard-YYYYmmdd-HHMMSS â
â
⢠python3 plistlib ã§æ£ããå€ã«æžãæ»ã â
d['StartCalendarInterval'] = [ â
{'Hour': int(h), 'Minute': minute} â
for h in hours.split(',') â
] â
â
⣠plutil -lint ã§æŽåæ§æ€èšŒ â
OK â launchctl bootout â sleep 1 â
â launchctl bootstrap â 埩å
ãã° â
NG â "ðŽ åŸ©å
åŸã®plistãå£ããŠãã"ãã° â
â æå察å¿ã«å§ãã â
ã³ã¢ããžãã¯ã®å®è£ ïŒplistèªã¿åãããŒã
ã¹ã¯ãªããã¯ã·ã§ã«ã®forã«ãŒãã§4æ¬ã®ãžã§ããé çªã«åŠçããŸããåååŸ©ã®æåã«ããããšã¯ãplistãpython3ã®ãã¢ããã¥ã¡ã³ãã§èªã¿åããHour:Minuteã®CSVãæšæºåºåã«åãããšã§ãã
actual="$(python3 - "$plist" <<'PY'
import plistlib, sys
try:
with open(sys.argv[1],'rb') as f: d = plistlib.load(f)
rows = d.get('StartCalendarInterval') or []
if isinstance(rows, dict): rows = [rows]
print(','.join(f"{r.get('Hour')}:{r.get('Minute')}" for r in rows))
except Exception as e:
print('ERR')
PY
)"
open(sys.argv[1], 'rb')ã§ãã€ããªã¢ãŒãã§éããŠããã®ã¯ãplistãXML圢åŒã§ããã€ããªåœ¢åŒã§ãplistlib.load()ãèªåå€å®ããããã§ããrows = d.get('StartCalendarInterval') or []ãšããŠããã®ã¯ããšã³ããªãååšããªãå Žåã®None察çã§ãããŸãisinstance(rows, dict)ã®ãã§ãã¯ã¯ãStartCalendarIntervalãé
åã§ã¯ãªãåäžã®<dict>ãšããŠèšè¿°ãããŠããå ŽåïŒ1æå»ã®ã¿ç»é²ããã±ãŒã¹ïŒã«å£ããªãããã®é²è¡åŠçã§ãã
åºåãããactualã¯"8:20,10:20,12:20,14:20,16:20,18:20,20:20,22:20"ã®ãããªæååã«ãªããŸãã
ã³ã¢ããžãã¯ã®å®è£ ïŒæåŸ å€çæãšæ¯èŒããŒã
SPECSé
åã®åãšã³ããªã¯"label:minute:hours"ãšããç¬èªåœ¢åŒã§å®çŸ©ãããŠããŸãã
SPECS=(
"com.lily.outreach-ig:20:8,10,12,14,16,18,20,22"
"com.lily.outreach-th:38:9,11,13,15,17,19,21"
"com.lily.followers-outreach:35:8,10,12,14,16,18,20,22"
"com.lily.outreach-yt:52:10,12,14,16,18,20"
)
ããããlabelã»minuteã»hoursãã·ã§ã«ã®ãã©ã¡ãŒã¿å±éã§åè§£ããexpectedãçµã¿ç«ãŠãŸãã
label="${spec%%:*}"; rest="${spec#*:}"
minute="${rest%%:*}"; hours="${rest#*:}"
ç¶ããŠIFS=','ã§æéã®é
åãåå²ãã"Hour:Minute"圢åŒã®CSVãçæããŸããoutreach-igãªãexpectedã¯"8:20,10:20,12:20,14:20,16:20,18:20,20:20,22:20"ã«ãªããŸãã
[ "$actual" = "$expected" ] && continueã®1è¡ãå€å®ã®æ¬äœã§ããäžèŽããã°ãã®ãžã§ãã¯çŽ éãããæ¬¡ã®ãžã§ããžãäžäžèŽããã£ãç¬éãããforensicèšé²ãšåŸ©å
ã®ã·ãŒã±ã³ã¹ãèµ°ããŸãã
ã³ã¢ããžãã¯ã®å®è£ ïŒåŸ©å ããŒã
æžãæ»ããèªã¿åããšåããpython3ã®ãã¢ããã¥ã¡ã³ãã§è¡ããŸãã
python3 - "$plist" "$minute" "$hours" <<'PY'
import plistlib, sys
path, minute, hours = sys.argv[1], int(sys.argv[2]), sys.argv[3]
with open(path,'rb') as f: d = plistlib.load(f)
d['StartCalendarInterval'] = [{'Hour': int(h), 'Minute': minute} for h in hours.split(',')]
with open(path,'wb') as f: plistlib.dump(d, f)
PY
sys.argv[2]ã§minuteãåãåãint()ã«ãã£ã¹ãããã®ã¯ãSPECSæååããæ¥ãå€ãstråã ããã§ããplistlib.dump(d, f)ã¯ããã©ã«ãã§XML圢åŒïŒfmt=plistlib.FMT_XMLïŒã§æžãåºããããå
ã®ãã€ããªplistãXMLã«å€æããŠããŸãå¯äœçšããããŸãããã ãlaunchdã¯XML圢åŒã®plistãåé¡ãªãèªã¿èŸŒããããå®çšäžã¯æ¯éãããŸããã
æžãæ»ãåŸã¯plutil -lint "$plist"ã§XMLæ§é ãæ€èšŒããŸããlaunchdã«å£ããplistãé£ããããšäºæããªãåäœãåŒãèµ·ããããããã®ã²ãŒãã¯çç¥ã§ããŸãããæ€èšŒãéã£ãå Žåã®ã¿ã以äžã®é åºã§launchdãžåç»é²ããŸãã
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null
sleep 1
launchctl bootstrap "gui/$(id -u)" "$plist"
bootoutãšbootstrapã®éã«sleep 1ãæãã®ã¯launchdãå
éšç¶æ
ãæŽæ°ããç¶äºãäžããããã§ãããããçããšbootstrapããšã©ãŒãè¿ãã±ãŒã¹ããããŸããbootoutã®å€±æã¯2>/dev/nullã§ç¡èŠããŠããã®ã¯ããã§ã«unloadãããŠããå Žåã§ãbootstrapããéãã°åé¡ãªãããã§ãã
forensicãã°ã®å®è£
æžãæããæ€ç¥ããç¬éã«æ®ãæ å ±ã¯3çš®é¡ã§ãã
log "$label: æžãæãæ€ç¥ mtime=$(stat -f '%Sm' -t '%F %T' "$plist")"
log "$label: çŸåš = $actual"
log "$label: ããã¹ã= $expected"
ps -Ao pid,lstart,comm | tail -n +2 | \
grep -iE "python|node|bash|launchctl|plutil" | tail -25 | \
while read -r l; do log "$label: ps> $l"; done
stat -f '%Sm' -t '%F %T'ã¯macOSåºæã®æžåŒã§ãplistã®æçµæŽæ°æå»ãYYYY-MM-DD HH:MM:SS圢åŒã§ååŸããŸãããããããã€æžãæããããããã®å¯äžã®ç©çç蚌æ ã§ãã
psã®ãã£ã«ã¿ã§python|node|bash|launchctl|plutilã察象ã«ããŠããã®ã¯ãplistãæžãæããããææ®µããããããããã®ã³ãã³ãã«éãããããã§ããtail -25ã§æå€§25è¡ã«çµãããã°ãè¥å€§åããªãããã«ããŠããŸãã
8æ23æ¥12æ04åã®å®éã®ã€ã³ã·ãã³ãã§ã¯ããã®forensicãã°ãæ®ã£ãŠããã°å®¹çããã»ã¹ã®åè£ãçµããã¯ãã§ããç®èãªããšã«ãã¬ãŒãã¹ã¯ãªããèªäœã¯ã€ã³ã·ãã³ãã®åŸã«äœã£ããã®ãªã®ã§ãåœæã®psã¹ãããã·ã§ããã¯ãããŸããããäœã£ãŠããã°ããã£ãããšããåŸæããèšèšããã®ãããã®forensicèšé²ã®ä»çµã¿ã§ãã
å®è£ ã®è©³çް
set -uo pipefail ââ -e ãå€ããçç±
ã¹ã¯ãªããã®åé 㯠set -uo pipefail ã§ãã-e ããããŸããã
set -uo pipefail
export PATH="/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
æå㯠-euo pipefail ã§æžããŠããŸãããããšã©ãŒãåºããå³çµäºãããã¹ããã©ã¯ãã£ã¹ã®ã¯ãã ããã§ãããšããããforensicã®psåºåéšåã§ã¹ã¯ãªãããçªç¶æ»ãããã°ãåºãŸãããåå ã¯ããã§ãã
ps -Ao pid,lstart,comm | tail -n +2 | while read -r p rest2; do echo "$p $rest2"; done \
| grep -iE "python|node|bash|launchctl|plutil" | tail -25 \
| while read -r l; do log "$label: ps> $l"; done
grep -iE "python|node|bash|..." ã¯ããããããè¡ããŒãä»¶ã®ãšãçµäºã³ãŒã1ãè¿ããŸããset -e ç°å¢ã§ã¯çµäºã³ãŒãéãŒãïŒå³exitæ±ããªã®ã§ãæªããããã»ã¹ãäžä»¶ãèµ°ã£ãŠããªãæ£åžžãªç¶æ
ã§ã¹ã¯ãªãããæ»ã¬ããšãããã°ã§ãããforensicããŒãã¯ãäœããªããã°äœãåºåããªãããæ£ããåäœãªã®ã«ã-e ã®ããã§ãäœããªããã°æ»ã¬ãã«ãªã£ãŠããã
-e ãå€ã㊠pipefail ã ãæ®ãããšã§ãæå³ããªããµã€ã¬ã³ã倱æïŒãã€ãéäžã§ã®ãšã©ãŒïŒã¯æ€ç¥ãã€ã€ãgrep空æ¯ãã¯ãã®ãŸãŸç¶ç¶ã§ããããã«ãªããŸããã
PATH ã®æç€ºãšlog颿°
export PATH="/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
LOG="$HOME/.claude/logs/outreach-schedule-guard.log"
launchdãžã§ãã¯ãŠãŒã¶ãŒã®ã·ã§ã«ç°å¢ãç¶æ¿ããŸãããTerminalããPATHãéã£ãŠããããã«èŠããŠããlaunchdãèµ·åããã·ã§ã«ã§ã¯ /opt/homebrew/bin ãååšããªãããšããããŸããpython3 ã¯Homebrewçµç±ã§ã€ã³ã¹ããŒã«ããŠãããããPATHãæç€ºããªããšãžã§ãèµ·åçŽåŸã« python3: command not found ã§ãµã€ã¬ã³ãã«èœã¡ãŸãããã®çµéšãããlaunchdçšã®ã·ã§ã«ã¹ã¯ãªããã§ã¯å¿
ãåé ã§PATHãäžæžãããããšãçã«ããŸããã
ãã°é¢æ°ã¯ printf ããŒã¹ã§ãã
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$LOG"; }
æå㯠echo "$(date '+%F %T') $*" >> "$LOG" ãšæžããŠããŸãããããã echo ã¯ã·ã§ã«å®è£
ã -e ãªãã·ã§ã³ã®æç¡ã«ãã£ãŠæåãå€ãããŸãããã°ã¡ãã»ãŒãžã®æ«å°Ÿã«æžã蟌ãæååã \n ãå«ãå Žåãechoãå±éãããã©ããã§åºåãå€ãããŸããprintf ã¯ãã©ãŒãããæååãšå€ãæç¢ºã«åé¢ããã®ã§ããã°åºåã®æ¹è¡ã»ç©ºçœãã©ã®ç°å¢ã§ãåãã«ãªããŸãã
plistãååšããªãå Žåã®ã¬ãŒã
[ -f "$plist" ] || { log "$label: plist ãç¡ã"; continue; }
SPECSé åã«å®çŸ©ããã©ãã«ã®plistãå®éã«ååšããªãå Žåãpython3ã®open()ããšã©ãŒãåºããŠã¹ã¯ãªãããæ¢ãŸããŸããååšãã§ãã¯ãå ¥ããŠãã°ãæ®ããŠããæ¬¡ã®ãžã§ãã«é²ãããšããèšèšã«ããŠããŸãããplistãç¡ããããã°ã«æ®ãããšã§ãlaunchdåŽã§ã¢ã³ããŒãããã®ãããã¡ã€ã«ããšåé€ãããã®ããåŸãã倿ã§ããŸãã
ããã¯ã¢ããã®ããŒãã³ã°
埩å åã«çŸç¶ã®plistãå¿ ãã³ããŒããŸãã
cp "$plist" "$plist.bak-guard-$(date +%Y%m%d-%H%M%S)"
ããšãã° com.lily.outreach-ig.plist.bak-guard-20260823-120404 ãšãããã¡ã€ã«ãæ®ããŸããã¿ã€ã ã¹ã¿ã³ãããã¡ã€ã«åã«çŒã蟌ãçç±ã¯ãåãplistãè€æ°åæžãæãããããšãã«ãã©ã®æç¹ã®ãã®ããlsã ãã§ç¢ºèªã§ããããã§ããæ¡åŒµåã .bak ã§ãªã .bak-guard-æ¥æ ã«ããŠããã®ãæå³çã§ãlaunchdã誀ã£ãŠèªã¿èŸŒãŸãªããããplistãšããŠèªèããã«ããååã«ããŠããŸãã
ãªãçŸåšãããã¯ã¢ããã®èªååé€åŠçã¯å
¥ããŠããŸãããåãplistãçæéã«äœåºŠãæžãæãããããš .bak ã墿®ããŸãããããã¯ããã§ãæžãæããç¹°ãè¿ãããŠããããšãã蚌æ ãšããŠã®äŸ¡å€ãããã®ã§ãåé€ã¯æåã«ããŠããŸãã
self-repairãžã§ãã®plistãæããŠãããããš
com.shun.self-repair.plist ã¯ãoutreach-schedule-guardã¹ã¯ãªããæ¬äœãå®æèµ·åããlaunchdãžã§ãã®plistã§ãããã®plistãçŽæ¥èªããšãããã¯ã°ã©ãŠã³ããžã§ãã®èšèšãšããŠåèã«ãªãå€ãããã€ããããŸãã
<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>
<key>RunAtLoad</key>
<false/>
LowPriorityIO: true ã¯IOã®åªå
床ãäžããä»ã®ããã»ã¹ã®ãã£ã¹ã¯ã¢ã¯ã»ã¹ã劚ããªãèšå®ã§ããã¬ãŒãã¹ã¯ãªããã¯plistã®read/writeãšãã°æžã蟌ã¿ããããªãã®ã§ãäœIOåªå
ã§åé¡ãããŸããã
Nice: 10 ã¯CPUåªå
床ã10段éäžããŸãã0ãããã©ã«ãã§å€ã倧ããã»ã©åªå
床ãäœããªããŸããç£èŠãžã§ããCPUãé£ãèãããŠã¡ã€ã³ã®å¶æ¥DMã¹ã¯ãªãããé
ãããŠã¯æ¬æ«è»¢åãªã®ã§ãæç€ºçã«äžããŠããŸãã
ProcessType: Background ã¯macOSã®Activity Monitorã§ãããã¯ã°ã©ãŠã³ããã«ããŽãªã«åé¡ãããããšã瀺ããŸããLowPriorityIO ãšã®çµã¿åããã§ãOSã®ããããªãŒã»CPUæé©åã®å¯Ÿè±¡ã«å«ãŸããŸãã
RunAtLoad: false ã¯èµ·åçŽåŸã®å³æå®è¡ãããªãèšå®ã§ããlaunchdã¯ãžã§ããããŒãããç¬éã«äžåºŠããªã¬ãŒããæ©èœãæã£ãŠãããtrue ã«ãããš launchctl bootstrap ããç¬éã«èµ°ããŸããã¬ãŒãã¹ã¯ãªããã¯StartCalendarIntervalã§å®å»ã«èµ°ãã°ååãªã®ã§ãäžèŠãªå³æå®è¡ã¯åã£ãŠããŸãã
StartCalendarInterval ã¯9:20ã»13:30ã»19:30ã®1æ¥3åã§ããå¶æ¥DMã8æãã22æãŸã§åããŠããã®ã«å¯ŸããŠã¬ãŒãã3åã ãã«ããŠããã®ã¯ãæžãæãã¯é£ç¶çã«çºçãããã®ã§ã¯ãªããäžæã«æžãæããããŠãã°ããæ°ã¥ããªãããã¿ãŒã³ã ããã§ãã1æ¥3åã§ååã«æ€ç¥ã§ãããããã§ãã¯èªäœãplistãread/writeããã®ã§ãã®åãæå°åããŠããŸãã
ç§ãè©°ãŸã£ã話
è©°ãŸãâ ïŒpython heredocã倿°å±éããŠå£ãã
æåã«æžããã³ãŒã㯠<<'PY' ã§ã¯ãªã <<PY ã§ããã
# NG: ã¯ã©ãŒããªããã¢ããã¥ã¡ã³ã
actual="$(python3 - "$plist" <<PY
import plistlib, sys
...
PY
)"
ã¯ã©ãŒããªãã® <<PY ã¯ã·ã§ã«ããã¢ããã¥ã¡ã³ãã®äžèº«ã倿°å±éããŸããPythonã³ãŒãã®äžã« $() ã $倿° ãäžåãªããŠããããšãã° sys.argv[1] ã® [1] ãzshç°å¢ã§ã°ããå±éã®å¯Ÿè±¡ã«ãªãã±ãŒã¹ããããŸããå®éã«ã¯ [1] ã¯å±éãããŸããã§ããããå¥ã®è¡ã§ f"{r.get('Hour')}:{r.get('Minute')}" å
ã® { ãš } ãã·ã§ã«ã«è§£éãããŠSyntax errorãåºãŸããã
<<'PY' ãšã·ã³ã°ã«ã¯ã©ãŒãã§å²ãããšã§ããã¢ããã¥ã¡ã³ãã®å
容ããã®ãŸãŸPythonã€ã³ã¿ãŒããªã¿ã«æž¡ãããŸãã以æ¥ãBashããpython/ruby/nodeã®ã³ãŒãããã¢ããã¥ã¡ã³ãã§æž¡ããšãã¯å¿
ã <<'EOXX' 圢åŒã«ããŠããŸãã
è©°ãŸãâ¡ïŒStartCalendarInterval ãåäžãšã³ããªã®ãšã dict ãè¿ã
plistlib ã§plistãèªãã ãšããStartCalendarInterval ã®å€ã¯éåžžlistïŒPythoné
åïŒã§ãããããplistã« <array> ã¿ã°ãªãã§ <dict> ãçŽæ¥æžãããŠããå Žåãplistlibã¯dictãè¿ããŸãã
<!-- 1æå»ã ãç»é²ããplistã®äŸ -->
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>10</integer>
<key>Minute</key><integer>0</integer>
</dict>
æå㯠rows = d.get('StartCalendarInterval') or [] ãšã㊠for r in rows ãåããŠããŸãããdictãã€ãã¬ãŒããããš 'Hour' ãš 'Minute' ãšããæååããŒãè¿ãã®ã§ãr.get('Hour') ã¯æååã«å¯Ÿããgetã«ãªãNoneãåºãŸããåºå㯠"None:None" ã«ãªããæ¯èŒã¯åœç¶äžäžèŽâæ¯å埩å
åŠçãèµ°ãããšããç¡éã«ãŒãç¶æ
ã«ãªããŸããã
# ä¿®æ£åŸ
rows = d.get('StartCalendarInterval') or []
if isinstance(rows, dict): rows = [rows]
isinstance(rows, dict) ãã§ãã¯ã§åäždictããªã¹ãã«å
ã1è¡ã解決çã§ããããããå
¥ãããŸã§ã1æå»ã ãèšå®ãããã¹ãçšplistã䜿ã£ãŠåäœç¢ºèªãããã³ã«åŸ©å
ãã°ãåºç¶ããŠããã®ã§ãæåã¯ããžãã¯ã®ãã°ã ãšæã£ãŠäœæéãå¥ã®å Žæãæ¢ãåããŸããã
è©°ãŸãâ¢ïŒ${expected:+,} ãç¥ããã«å
é ã«ã³ããå
¥ãç¶ãã
æåŸ å€ã®CSVçæã§æåã«æžããã³ãŒãã¯ããã§ãã
expected=""
for h in "${harr[@]}"; do expected="${expected},${h}:${minute}"; done
com.lily.outreach-ig ã®å Žåãã«ãŒããåããš expected 㯠",8:20,10:20,12:20..." ã«ãªããŸããå
é ã«ã«ã³ããä»ããŸããåœç¶ actualïŒ"8:20,10:20,..." ïŒãšäžèŽããŸãããæ¯åäžäžèŽâæ¯å埩å
åŠçããšããç¶æ
ã«ãªããã¬ãŒãã¹ã¯ãªããèªäœãæ¯åplistãæžãæããŠããŸããã
Bashã®å€æ°å±é ${var:+value} ã¯ãvarã空ã§ãªããã°valueãå±éã空ãªãã°ç©ºæåããšããæå³ã§ãã
for h in "${harr[@]}"; do expected="${expected}${expected:+,}${h}:${minute}"; done
${expected:+,} ãšããããšã§ããã§ã«äœããããšãã ãã«ã³ããå
ã«çœ®ããåäœã«ãªããŸããæåã®èŠçŽ ã§ã¯expectedã空ãªã®ã§ã«ã³ãã¯æ¿å
¥ãããã2çªç®ä»¥é㯠, ãå
¥ããçµæãšã㊠"8:20,10:20,12:20,14:20,16:20,18:20,20:20,22:20" ãšããæ£ããCSVã«ãªããŸãã
Bashã®ãã©ã¡ãŒã¿å±é㯠:-ïŒããã©ã«ãå€ïŒã :=ïŒä»£å
¥ïŒã¯ããç¥ã£ãŠããã®ã«ã:+ïŒé空æã®ã¿å±éïŒã¯ãã®ãã°ãèžããŸã§æèããããšããããŸããã§ããã
è©°ãŸãâ£ïŒlaunchctl bootstrap ãå³åº§ã«ãšã©ãŒãè¿ãã
埩å
åŠçã®æåã®ããŒãžã§ã³ã¯ bootout ã®çŽåŸã« bootstrap ãåŒãã§ããŸããã
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null
launchctl bootstrap "gui/$(id -u)" "$plist" # sleep ãªã
ãã®é çªã§å®è¡ãããšãbootstrap ã Load failed: 5: Input/output error ãè¿ããŠå€±æããŸãããlaunchd㯠bootout ãåãåã£ãåŸãå
éšçã«ãžã§ãã®çµäºåŠçãéåæã§å®äºãããŸããbootout ã®è¿ãå€ã0ã§ããlaunchdã®å
éšç¶æ
ããŸã ã¯ãªã¢ãããŠããªãç¬éã« bootstrap ãå©ããšç«¶åããŠãšã©ãŒã«ãªããŸãã
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null
sleep 1
if launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null; then
log "$label: 埩å
ããŠåèªèŸŒãã"
else
log "$label: ðŽ bootstrap ã«å€±æããïŒæå確èªãèŠãïŒ"
fi
sleep 1 ãæãããšã§è§£æ¶ããŸãããã1ç§åŸ
ã€ããšãã解決çã¯ãšã¬ã¬ã³ãã§ã¯ãããŸããããlaunchdã®ããã¥ã¡ã³ãã«ã¯éåæå®äºã®ä¿èšŒã«é¢ããèšè¿°ããªããçµéšçã«1ç§ãå®å
šããŒãžã³ãšããŠååã§ããããšã確èªã§ããããããã以äžèª¿ã¹ãæéããããã«æ¡çšããŸããã
çŸå Žã§åãããŠã¿ããšããªããåããªããâãsleep 1ãå
¥ãããåããããšãã解決ã¯åŸã
ã«ããŠçç±ãäžéæãªãŸãŸå
ã«é²ã¿ãã¡ã§ãããããã§ã¯ãã°ã« ðŽ bootstrap ã«å€±æããïŒæå確èªãèŠãïŒ ãæ®ãããšã§ãä»®ã«å床倱æãããšãã«å³æ°ã¥ããå®å
šç¶²ãäœã£ãŠããŸãã
è©°ãŸãâ€ïŒpsãã€ãã§ read -r p rest2 ã® rest2 ãå¿
èŠã ã£ãçç±
forensicã®psåŠçã¯ãå®ã¯äžåºŠã·ã³ãã«ã«æžãããšããŠããŸããããŸããã§ããã
# æåã®ãã€ãŒããªå®è£
ps -Ao pid,lstart,comm | grep -iE "python|node|bash|launchctl|plutil" | tail -25
ããã§ãåãããã«èŠããŸããããgrep ã« bash ãå«ããŠããããããã€ããåŠçããŠãã bash ããã»ã¹èªäœãgrepã«ãããããŠåºåã«æ··å
¥ããŸããããŸããpsã®åºåãã©ãŒããããç°å¢ã«ãã£ãŠåå¹
ãå€ãããããwhileã§ãã£ãŒã«ããèªã¿åãéã« read -r l ã ãã ãšè¡æ«ã®ç©ºçœãŸã§å€æ°ã«å
¥ã£ãŠãã°ãæ±ããªããŸããã
å®éã®ã³ãŒãã¯2段éã®ãã€ãã«ãªã£ãŠããŸãã
ps -Ao pid,lstart,comm | tail -n +2 | while read -r p rest2; do echo "$p $rest2"; done \
| grep -iE "python|node|bash|launchctl|plutil" | tail -25 \
| while read -r l; do log "$label: ps> $l"; done
while read -r p rest2 ã® rest2 ã¯äœ¿ã£ãŠããªãããã«èŠããŸããããããéèŠã§ããread -r p rest2 ãšããããšã§ p ã«æåã®ãã£ãŒã«ãïŒPIDïŒãå
¥ããrest2 ã«æ®ãå
šãã£ãŒã«ããå
¥ããŸãããã®åŸ echo "$p $rest2" ã§ååºåããããšã§ãè¡é ã®ãããè¡ïŒtail -n +2 ã§é€å»æžã¿ïŒãé€ããæŽåœ¢æžã¿ã®è¡ã«ãªããŸããread -r l ã ãã§å
šè¡ãåãåããšãpsã®ãã©ãŒãããç±æ¥ã®äœåãªã¹ããŒã¹ãä¿æãããŸãããp rest2 ã§äžåºŠåè§£ããŠåçµã¿ç«ãŠããããšã§é£ç¶ã¹ããŒã¹ãã·ã³ã°ã«ã¹ããŒã¹ã«æ£èŠåãããŸãã
çµæãšã㊠forensicãã°ã® ps> è¡ã¯ 12345 Fri Aug 23 12:04:11 2026 python3 ãšããèªã¿ããã圢åŒã§æ®ããŸãã8æ23æ¥ã®ã€ã³ã·ãã³ããèµ·ãããšãããã®ãã©ãŒããããå®ãŸã£ãŠããã°çã£å
ã«ãã®pidã»å®è¡æå»ã»ã³ãã³ãåãçªãåãããããšãã§ããã¯ãã§ãã
次ã®ããŒãã§ã¯ããã®ã¬ãŒãã¹ã¯ãªããèªäœãlaunchdã«ç»é²ããæé ãšãå®éã«ã¬ãŒããåãããšãïŒãã¹ãã§æå³çã«plistãæžãæããŠç¢ºèªãããšãïŒã®ãã°åºåã®å®äŸãèŠãŠãããŸãã
ã€ãŸãããã€ã³ã
p2ã§æãã5ã€ã®è©°ãŸãã«å ããŠãå®éã«äœããªããã¶ã€ãã£ãåé¡ããŸãšããŸããç®æ¡æžãã§ç¶²çŸ ããŸãã
â self-repair.plistã®ProgramArgumentsãã¹ã¯ãªããçŽåŒã³ã§ãªãã£ã
com.shun.self-repair.plistãåããŠèªãã ãšããProgramArgumentsãæ³å®ãšéããŸããã
<array>
<string>~/.claude/scripts/claude-quota-guard.py</string>
<string>--job</string>
<string>com.shun.self-repair</string>
<string>--</string>
<string>/bin/bash</string>
<string>~/.claude/scripts/self-repair.sh</string>
</array>
/bin/bash self-repair.shãçŽæ¥åŒã¶ã®ã§ã¯ãªããclaude-quota-guard.pyãçµç±ããŠããŸããæåã¯ããªãäžæåãŸããŠããã®ãããåããããçŽ çŽã«/bin/bash outreach-schedule-guard.shãçŽæ¥ProgramArgumentsã«æžããŠãã¹ãããŠããŸãããã¬ãŒãã¹ã¯ãªããèªäœãClaude APIãåŒã¶åŠçïŒå°æ¥ã®æ¡åŒµå«ãïŒãæã€ããšãæ³å®ããã¯ã©ãŒã¿ç®¡çã®åã®äžã«çœ®ãããã®èšèšã§ããã¬ãŒãç³»ã®ãžã§ãã¯å
šéšãã®ã©ãããŒçµç±ã«ããã«ãŒã«ãåŸããèšããŸããã
â¡ launchctl listã§ãžã§ãã"running"ãªã®ã«PIDã-ã®ãŸãŸ
launchctl bootstrapãæåããçŽåŸã«launchctl list | grep com.shun.self-repairã確èªãããšãPIDã-ã§statusã0ã§ããããããŒãã«å€±æããïŒããšçŠããŸããããããã¯æ£åžžåäœã§ããRunAtLoad: falseãã€StartCalendarIntervalã§ã®å®å»å®è¡èšå®ã®å Žåãlaunchdã¯ãžã§ããç»é²ããã ãã§æ¬¡ã®å®å»ãŸã§èµ·åããŸãããPIDã-ã¯ãåŸ
æ©äžããæå³ããŸããå®éã«9:20ã»13:30ã»19:30ã®ããããã®æå»ãéããŠããããäžåºŠlaunchctl listã確èªããŠã¯ãããŠPIDã®å€ãå
¥ããŸããPIDã§çæ»ã確èªããã®ã§ã¯ãªããå®å»åŸã«ãã°ãã¡ã€ã«ãtail -fã§èŠåŒµãã®ãæ£ããç¢ºèªæé ã§ãã
⢠EnvironmentVariablesã®HOMEãæç€ºããªããš$HOMEã空ã«ãªã
plistã«EnvironmentVariablesãããã¯ãå
¥ããåã¯ãself-repair.shã®äžã§$HOME/.claude/logs/ãžã®ãã¹ã空æååã«ãªããã°ãçºçããŸãããlaunchdã¯GUI sessionã®ãŠãŒã¶ãŒç°å¢å€æ°ãéšåçã«ããåŒãç¶ãããHOMEãæªå®çŸ©ã®ç¶æ
ã§ã·ã§ã«ã¹ã¯ãªãããèµ·åããã±ãŒã¹ããããŸãã
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/å®ãŠãŒã¶ãŒå</string>
<key>LANG</key>
<string>en_US.UTF-8</string>
<key>PATH</key>
<string>/Users/å®ãŠãŒã¶ãŒå/.nvm/versions/node/v24.13.0/bin:/opt/homebrew/bin:...</string>
</dict>
HOMEã¯~å±éãæ©èœããªãplistå
ã§ã¯çµ¶å¯Ÿãã¹ã§æžããããããŸãããnvmçµç±ã®Node.jsã䜿ãã¹ã¯ãªãããåŒã¶å ŽåãPATHã«ã~/.nvm/versions/node/vXX.X.X/binãå«ããå¿
èŠããããŸãããããå¿ãããšnode: command not foundãStandardErrorPathã®ãã°ã«éãã«ç©ãŸããŸãã
⣠StandardOutPathã»StandardErrorPathã®ãã£ã¬ã¯ããªãååšããªããšãžã§ããèµ·åããªã
plistã«ä»¥äžãæžããŠãããªããããã£ã¬ã¯ããªãå ã«äœã£ãŠããªãã£ãããšããããŸãã
<key>StandardErrorPath</key>
<string>~/.claude/logs/self-repair.launchd.err.log</string>
<key>StandardOutPath</key>
<string>~/.claude/logs/self-repair.launchd.log</string>
~/.claude/logs/ãååšããªãç¶æ
ã§launchctl bootstrapãããšããžã§ãã®ããŒãã¯æåïŒlaunchctl listã§ã衚瀺ãããïŒã«ãããããããå®å»ã«ã¹ã¯ãªãããå®è¡ãããŸããããšã©ãŒãã°ãæžããªãã®ã§äœãæ®ããããã ãåããŠããªãããšããçµæã ããæ®ããŸãã解決çã¯mkdir -pãå
ã«å®è¡ããããšãšãoutreach-schedule-guard.shåé ã®mkdir -p "$(dirname "$LOG")"ãã¿ãŒã³ãplistç»é²åã®ã»ããã¢ããã¹ã¯ãªããã«ãçµã¿èŸŒãããšã§ãã
†plistlib.dump()ããã€ããªplistãXMLã«å€æããŠããŸã
æžãæ»ãåŠçã§ã¯plistlib.dump(d, f)ãããã©ã«ãåŒæ°ã§åŒãã§ããŸããããã©ã«ãã¯fmt=plistlib.FMT_XMLãªã®ã§ãå
ã®plistããã€ããªåœ¢åŒïŒplutil -convert binary1ã§å€ææžã¿ã®ãã®ïŒã§ããæžãæ»ããåŸã¯XML圢åŒã«ãªããŸãã
with open(path,'wb') as f: plistlib.dump(d, f)
# â ããã©ã«ãã¯XML圢åŒã§åºåããã€ããªç¶æãããå Žåã¯:
with open(path,'wb') as f: plistlib.dump(d, f, fmt=plistlib.FMT_XML)
# ãŸãã¯
with open(path,'wb') as f: plistlib.dump(d, f, fmt=plistlib.FMT_BINARY)
launchdã¯XML圢åŒããã€ããªåœ¢åŒãåé¡ãªãèªãããå®å®³ã¯ãããŸããããplutil -pã§äžèº«ã確èªãããšãã®ãã©ãŒããããå€ãããŸããããªããæžãæããããïŒããšããã¯ã¢ãããšæ¯èŒããŠæ··ä¹±ããããšããããŸããå
ãã©ãŒããããç¶æãããå Žåã¯èªã¿èŸŒã¿æã«ãã€ããªãåŠããå€å®ããŠããæžãåºããã©ãŒããããåå²ããå¿
èŠããããŸããä»åã¯ãlaunchdãèªããã°XMLã§çµ±äžããšãã倿ã§å²ãåããŸããã
⥠ããã¯ã¢ãã.bakãã¡ã€ã«ãlaunchdã誀ã£ãŠãžã§ããšããŠèªã
LaunchAgentsãã£ã¬ã¯ããªã«æ¡åŒµå.plistã®ãã¡ã€ã«ãããã°ãmacOSã®launchdç®¡çæ©èœã¯ããããžã§ãåè£ãšããŠèªèããŸããããã¯ã¢ãããã¡ã€ã«ã«.plistæ¡åŒµåãä»ããŠããåæããŒãžã§ã³ã§ã¯ãcom.lily.outreach-ig.plist.bakã§ã¯ãªãcom.lily.outreach-ig.bak.plistãšããåœåãããŠããŸããlaunchd GUIããŒã«ïŒLaunchControlçïŒãããããžã§ããšããŠè¡šç€ºãããšããæ··ä¹±ãçºçããŸãããçŸåšã®åœåèŠå$label.plist.bak-guard-YYYYmmdd-HHMMSSã¯ã.plistã§çµãããªãããšãæå³çã«ç¢ºä¿ããŠããŸãã
⊠forensicã®psãã£ã«ã¿ã§bashãå«ãããšèªåèªèº«ãããããã
grep -iE "python|node|bash|launchctl|plutil"
ãã®ãã¿ãŒã³ã§bashãå«ããŠããããããã€ãåŠçãæ
åœããŠããbashããã»ã¹èªèº«ãgrepã«ãããããŸãããã°ã«ã¯/bin/bash /path/to/outreach-schedule-guard.shãšããè¡ãå¿
ãåºåãããŸããããã¯ãã€ãºã§ããããããŠé€å€ããŠããŸããããã¬ãŒãã¹ã¯ãªããèªèº«ã®èµ·åæå»ãpsã«æ®ã£ãŠããããšããäºå®ãã®ãã®ã蚌æ ã«ãªãããããã§ãããã ããpsã®ã¹ãããã·ã§ãããèŠããšããbashãã®è¡ã¯ã¬ãŒãã¹ã¯ãªããèªèº«ã ãšè§£éããã®ãã»ãŒç¢ºå®ã§ãã
â§ ã¬ãŒãã®å®è¡é »åºŠãšDMã®éä¿¡é »åºŠã®ã®ã£ãã
com.shun.self-repair.plistã®StartCalendarIntervalã¯9:20ã»13:30ã»19:30ã®3åã§ããäžæ¹ãå¶æ¥DMã¯com.lily.outreach-igã8:20ãã22:20ãŸã§2æéããã«8ååããŠããŸãããã¬ãŒããæåŸã«åããã®ã19:30ã§ãæžãæãã20:00ã«èµ·ããå Žåãæ¬¡ã®æ€ç¥ã¯ç¿æ9:20ãŸã§13æéãããããšããèšç®ã«ãªããŸãã
ãã®èšèšäžã®æ¬ ç¹ã¯èªèæžã¿ã§ãã察çãšããŠãã¬ãŒãã®å®è¡é »åºŠãäžããïŒäŸãã°1æéããã«ããïŒããå¶æ¥DMã¹ã¯ãªããèªäœã«èµ·ååã®èªå·±æŽåãã§ãã¯ãçµã¿èŸŒãããæ€èšããŠããŸããçŸæç¹ã§ã¯ãæžãæããç¹°ãè¿ãããé »åºŠã¯é«ããªãããšããåæã§3åã«æããplistã®read/writeã«ããå¯äœçšãšã®ãã¬ãŒããªããåã£ãŠããŸãã8æ23æ¥ã®ã€ã³ã·ãã³ãã§ã¯ãæ°æ¥éæ°ã¥ããªãã£ãããšããå®å®³ããã£ãã®ã§ããã®å€æã¯å°æ¥èŠçŽãå¯èœæ§ããããŸãã
âš stat -f '%Sm'ã®ãã©ãŒãããã¯macOSåºæ
forensicãã°ã§mtimeãååŸããŠããstat -f '%Sm' -t '%F %T'ã¯ãmacOSã®statã³ãã³ãã®æžåŒã§ããGNU Linuxç°å¢ã§ã¯stat -c '%y'ãåçã§ããããã®ã¹ã¯ãªããã¯macOSå°çšãªã®ã§åé¡ãããŸããããã ããä»ã®macã䜿ã£ãŠããååã«ããã®ã¹ã¯ãªããLinuxã§ãåãïŒããšèããããšããstatã®éšåã¯åããªããšäŒããå¿
èŠããããŸããã¯ãã¹ãã©ãããã©ãŒã åãããªãpythonåŽã§os.path.getmtime()ã䜿ã£ãŠçµ±äžããéžæè¢ããããŸãã
ãã¹ããã©ã¯ãã£ã¹
å®è£ ãšéçšãéããŠåŸãå€æåºæºããŸãšããŸãã
1. launchdçšã·ã§ã«ã¹ã¯ãªããã®åé ã§PATHãäžæžããã
export PATH="/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
ã¿ãŒããã«ã§ã¯åãã®ã«launchdããã ãšcommand not foundã«ãªãã»ãŒãã¹ãŠã®åå ãPATHã§ããnvmé
äžã®NodeãHomebrewã®python3ã¯ã·ã¹ãã PATHã«ååšããŸãããlaunchdçšã¹ã¯ãªããã®ãã³ãã¬ãŒã1è¡ç®ã¯åžžã«ããã§ãã
2. set -eã¯å€ããŠpipefailã ãæ®ã
ç£èŠã»åŸ©å
ã¹ã¯ãªããã«ãããŠ-eã¯çŸå®³ãã£ãŠäžå©ãªãã§ããgrepã®ç©ºæ¯ãïŒçµäºã³ãŒã1ïŒã§ã¹ã¯ãªãããæ»ã¬ãšããp2ã§èª¬æããçç±ã«å ããŠãlaunchctl bootoutã®å€±æïŒãã§ã«unloadæžã¿ã®ãšãïŒã-eç°å¢ã§ã¯èŽåœçã«ãªããŸããpipefailã ãæ®ãã°ããã€ãéäžã®ãµã€ã¬ã³ããšã©ãŒã¯æ€ç¥ã§ããŸãã
3. plistã®èªã¿æžãã¯python3 plistlibã§è¡ã
ã·ã§ã«ã®grep/awkã«ããXMLããŒã¹ã¯åä¿èšŒããªãããã€ããªplistã«ã¯ãŸã£ãã察å¿ã§ããŸãããpython3ã¯æšæºã©ã€ãã©ãªã®plistlibã§XML/ãã€ããªäž¡åœ¢åŒãèªåå€å®ã§ããHourãšMinuteã確å®ã«æŽæ°ãšããŠåãåºããŸããpython3 -c "import plistlib" ã1è¡ã§å®çµãããããå€éšäŸåãŒãã§ãã
4. ãã¢ããã¥ã¡ã³ãã¯å¿
ã<<'EOF'圢åŒã§ã¯ã©ãŒããã
BashããPythonã³ãŒãããã¢ããã¥ã¡ã³ãã§æž¡ãéãã¯ã©ãŒããªã<<EOFã¯ã·ã§ã«ãPythonã³ãŒãã倿°å±éããããšããŠå£ããŸãã{ } $ãå«ãPythonã³ãŒãã¯ç¹ã«å±éºã§ãã<<'PY'ãšã·ã³ã°ã«ã¯ã©ãŒãã§å²ãç¿æ
£ãæåããæã€ããšã§ããããã°é£åºŠã®é«ããã°ãé²ããŸãã
5. 埩å åã«å¿ ãã¿ã€ã ã¹ã¿ã³ãä»ãããã¯ã¢ãããäœã
cp "$plist" "$plist.bak-guard-$(date +%Y%m%d-%H%M%S)"
埩å
åŠçã誀åäœãããšãïŒããšãã°SPECSé
åã®ãã¹ã§æ£ããèšå®ããã£ãšãäžäžèŽããšå€å®ãç¶ããå ŽåïŒãããã¯ã¢ããããªããã°å
ã®èšå®ã«æ»ããŸãããã¿ã€ã ã¹ã¿ã³ããçŒã蟌ãçç±ã¯ãè€æ°åã®æžãæããèµ·ãããšãã«æç³»åãls -ltã ãã§è¿œããããã§ãã
6. plutil -lintã§plistæ§é ãæ€èšŒããŠããlaunchdãžæž¡ã
æžãæ»ãåŸã«æ§é ãå£ããplistãlaunchdã«é£ããããšããžã§ããsilentã«èµ·åããªããªããŸãã
if plutil -lint "$plist" >/dev/null 2>&1; then
# launchctl bootstrap ãžé²ã
else
log "ðŽ åŸ©å
åŸã®plistãå£ããŠããïŒæ»ããŠããªãïŒ"
fi
ãã®ã²ãŒããããã°ãplistlibã®ãã°ãæžã蟌ã¿äžæã«ããäžéå端ãªXMLã§launchdãæ±æãããªã¹ã¯ãæé€ã§ããŸãã
7. bootoutãšbootstrapã®éã«sleep 1ãæã
launchdã®bootoutã¯éåæã§å®äºããŸããbootoutã®returnå€ã0ã§ããlaunchdå
éšã®ãžã§ãçµäºåŠçãçµãã£ãŠããªãç¬éã«bootstrapãå©ããšLoad failed: 5: Input/output errorãè¿ããŸãã1ç§ã®åŸ
æ©ã¯çµéšçã«å®å
šããŒãžã³ãšããŠååã§ããããã«bootstrapã®æåŠãifæã§åããŠãã°ã«æ®ãããšã§ãä»®ã«å倱æãããšãã«å³æ°ã¥ããŸãã
8. forensicãã°ïŒmtime + psã¹ãããã·ã§ããïŒãå¿ ãæ®ã
埩å ã ãããŠçµããå®è£ ã¯ãäœãèµ·ãããããéã«è¬ããŸããæžãæããæ€ç¥ããç¬éã®plistã®mtimeãšãå®è¡äžã®ããã»ã¹äžèŠ§ãæ®ããŠããããšã§ãåå ç©¶æã®æãããã«ãªããŸãã8æ23æ¥ã®ã€ã³ã·ãã³ãã§ãã¬ãŒãã¹ã¯ãªãããããã°å®¹çããã»ã¹ãç¹å®ã§ããããšããåŸæãããã®èšèšã®åæ©ã§ãããã°ããããã°ãåãæžãæããç¹°ãè¿ããããšãã«ãã¿ãŒã³ãèŠããŠããŸãã
9. printfããŒã¹ã®ãã°é¢æ°ãäœã
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$LOG"; }
echoã¯ã·ã§ã«å®è£
ãš-eãã©ã°ã®æç¡ã«ãã£ãŠæ¹è¡ã®æ±ããå€ãããŸãããã°ã¡ãã»ãŒãžã«ããã¯ã¹ã©ãã·ã¥å«ãæååãæ··å
¥ãããšããechoã ãšç°å¢ã«ãã£ãŠ\nãæ¹è¡ã«å±éãããããããªãã£ããããŸããprintfã®ãã©ãŒãããæååãšå€ã®åé¢ã¯äžè²«æ§ãä¿èšŒããŸãã
10. plistäžåšã®ã¬ãŒããå ¥ãã
[ -f "$plist" ] || { log "$label: plist ãç¡ã"; continue; }
SPECSé åã«å®çŸ©ããã©ãã«ã®plistãæ¶ããŠããå Žåãpython3ã®open()ãäŸå€ãåºããŠã¹ã¯ãªããå šäœãæ¢ãŸããŸããååšãã§ãã¯ïŒãã°ïŒæ¬¡ã«ãŒããžã®continueã«ããããšã§ã4æ¬ã®ãžã§ãã®ãã¡1æ¬ã ãplistãæ¶ããŠããŠãæ®ã3æ¬ã¯æ£åžžã«ãã§ãã¯ãèµ°ããŸãã
11. RunAtLoad: falseã§ç»é²çŽåŸã®å³æå®è¡ãé²ã
launchdã¯launchctl bootstrapã§ãžã§ããããŒãããç¬éã«äžåºŠã ãèµ·åããæ©èœïŒRunAtLoad: trueçžåœïŒãæã£ãŠããŸããã¬ãŒãã¹ã¯ãªããã¯å®å»ã«èµ°ãã°ååãªã®ã§ãRunAtLoad: falseã§äžèŠãªå³æå®è¡ãåã£ãŠããŸãã埩å
åŠçã¯åªçã§ãããplistã®read/writeã¯ãŒãã§ãªãã³ã¹ãããããŸãã
12. LowPriorityIOãšNice: 10ã§ç£èŠãžã§ãã®ãªãœãŒã¹åœ±é¿ãæå°åãã
com.shun.self-repair.plistã«ã¯ä»¥äžãèšå®ãããŠããŸãã
<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
<integer>10</integer>
<key>ProcessType</key>
<string>Background</string>
ç£èŠãžã§ããCPUã»IOãé£ãèãããŠãç£èŠå¯Ÿè±¡ã§ããå¶æ¥DMã¹ã¯ãªããã®ããã©ãŒãã³ã¹ãèœãšãã®ã¯æ¬æ«è»¢åã§ããNice: 10ã¯CPUåªå
床ã10段éäžãïŒ0ãããã©ã«ãïŒãLowPriorityIOã¯IOã®åªå
床ãäžããŸããProcessType: Backgroundãšã®çµã¿åããã§OSã®ããããªãŒã»CPUæé©åã®å¯Ÿè±¡ã«å«ãŸããŸãã
13. StartCalendarIntervalã®åäžãšã³ããªåé¡ãisinstanceãã§ãã¯ã§ã¬ãŒããã
rows = d.get('StartCalendarInterval') or []
if isinstance(rows, dict): rows = [rows]
ãã®1è¡ããªããã°ã1æå»ã ãèšå®ããplistïŒ<array>ã¿ã°ãªãã§<dict>ãçŽæžãïŒã«å¯ŸããŠæ¯å埩å
åŠçãèµ°ããŸããplistlibãdictãè¿ãã®ã listãè¿ãã®ãã¯XMLã®æ§é 次第ãªã®ã§ãé²è¡çã«ãã§ãã¯ããå¿
èŠããããŸãã
14. CSVã®å
é ã«ã³ãåé¡ã¯${var:+,}ã§è§£æ±ºãã
for h in "${harr[@]}"; do expected="${expected}${expected:+,}${h}:${minute}"; done
${expected:+,}ã¯ãexpectedã空ã§ãªããã°ã«ã³ããæ¿å
¥ã空ãªãã°äœãããªãããšããBashã®ãã©ã¡ãŒã¿å±éã§ããexpected="${expected},${h}:${minute}"ãšæžããšå
é ã«ã«ã³ããå
¥ããæåŸ
å€ãšå®å€ãæ°žé ã«äžèŽããªãç¡é埩å
ã«ãŒãã«ãªããŸãã
15. ã¬ãŒãèªèº«ã®launchdç»é²ã¯bootout â sleep 1 â bootstrapã®æé ã§æ¯åè¡ã
æ¢åã®ãžã§ããæŽæ°ãããšãïŒProgramArgumentsãStartCalendarIntervalãå€ãããšãïŒãlaunchctl unloadãbootoutãªãã«bootstrapããŠã倿Žãåæ ãããŸããã以äžã®æé ãã»ããã¢ããã¹ã¯ãªããã«ãŸãšããŠãããšãæ¯åè¿·ããã«æžã¿ãŸãã
launchctl bootout "gui/$(id -u)/com.shun.self-repair" 2>/dev/null
sleep 1
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.shun.self-repair.plist
launchctl list | grep self-repair
æåŸã®launchctl list確èªã§PIDã-ã§ãstatusã0ãªãæ£åžžç»é²ã§ãã
ãŸãšã
2026幎8æ23æ¥12æ04åã®ã¹ã±ãžã¥ãŒã«æžãæãããå§ãŸã£ããã®ä»çµã¿ã¯ãçµæçã«ãèªååã®çšŒåçã人éã®ç®ã§æ
ä¿ããªããŠããç°å¢ããäœãããã®äžæ©ã«ãªããŸãããå¶æ¥DM4æ¬ã¬ãŒã³ã1æ¥åèš29忣ããã¹ã±ãžã¥ãŒã«ã§åããŠããããããããã確èªããªããŠãoutreach-schedule-guard.shã9:20ã»13:30ã»19:30ã®3åãã§ãã¯ããŠç°å€ãããã°èªåã§æ»ããŸãã
ã³ã¢ã®å®è£ ã¯73è¡ã§ããplistlibã§èªãã§ãæåŸ å€ãšæ¯èŒããŠãéã£ããããã¯ã¢ããããŠæ»ããŠlaunchdã«åç»é²ããããã®éã«forensicãã°ãæ®ããããã ãã§ããè€éã«èŠããlaunchdã®å éšããXMLãçŽæ¥èªãã§æžããpython3ã®æšæºã©ã€ãã©ãªãããã°ãã·ã§ã«ã¹ã¯ãªããããå¶åŸ¡ã§ããŸãã
å人éçºã§æå120äžãç¶æããŠããã®ã¯ãã人éãèŠãŠããªãæéã«èªåŸã§åãç¶ããä»çµã¿ããç©ã¿äžããŠããããã§ããäžã€ã²ãšã€ã®ä»çµã¿ã¯ã·ã³ãã«ã§ãã§ãæ¢ãŸããšåçã«çŽçµãããããæ¢ãŸã£ããšãã«èªåã§çŽãèšèšã«ããããã®èšäºã§ç޹ä»ããã¬ãŒãã¹ã¯ãªããã¯ããã®ææ³ã®äžäŸã§ãã
ä»çµã¿ã®å šäœåã»æ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 ããð¬
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ