ð 埩垰åŸã®4é£å°ã mv 1è¡ã§æœ°ã: launchd ãã¥ãŒåŠçãåªçã«ãã
æåãŒãããå幎ãClaude Code ã§èªåŸç°å¢ãæ§ç¯ããŠä»æã¯æå120äžåã§ããä»çµã¿ãå£ããç¬éãããèšèšã®çããããåºãã«ãªããŸãã
ãªããã®ä»çµã¿ãå¹ãã®ã
ãäœæ¥ã§çŽãããšãç°å¢ã§çŽããã®åå²ç¹
èªååãå£ãããšã人ã¯åå°çã«ã次ã¯ãããããããšããäœæ¥ã¬ãã«ã®å¯Ÿçãæ¢ããŸããããã°ãããŸãã«ç¢ºèªããããæåã§ãªãã©ã€ããããããå°ãééã空ãããããã®çš®ã®å¯Ÿçã¯ãã¹ãŠãåé¡ãåçºããæã®å·ãå°ããããããã®åŠçœ®ã§ããåå ãæœ°ããŠããªãã®ã§ãåãæ¡ä»¶ãæãã°å¿ ãåçºããŸãã
èªååã·ã¹ãã ãå幎éçšããŠç¢ºä¿¡ããã®ã¯ãã¹ã±ãžã¥ãŒã©ãŒãšå ±æãã£ã¬ã¯ããªãçµã¿åãããæ§æã¯ãæä»å¶åŸ¡ãæç€ºçã«å®è£ ããªãéãå¿ ãç«¶åããããšããäºå®ã§ãããéãæªããã°ç«¶åãããã§ã¯ãªããç«¶åããæ¡ä»¶ãæãã°å¿ ãç«¶åãããã確çã®åé¡ã§ã¯ãªããæ§é ã®åé¡ã§ãã
2026幎9æ17æ¥ãCodex ãäžž1æ¥ usage_limit_exceeded ã§æ¢ãŸããŸããã~/.codex/sessions/2026/09/17/ é
äžã«ã¯54ã»ãã·ã§ã³ãç©ã¿äžãããå
šéšãäžéãšã©ãŒã§çµäºããŠããŸãããã®éãnote-autolikeã»ai-portraits-fragmentsã»social-autolike ã®3ã¯ãŒã¯ãããŒã«çºæ³šãæã¡ç¶ããçµæã~/dev/note-autolike/done/ ãã£ã¬ã¯ããªã«æªåŠçã® JSON ãç©ã¿äžãããŸãããç¿æ05:20ã®å®æž¬ã§ needs_imagegen_thumbnail ãã©ã°ä»ãã® JSON ã 43æ¬ãã57æ¬ ã«èšããã§ããŸãã14æ¬ãæ°èŠã«ç©ã¿äžããããã®ãã¹ãŠãæªåŠçã®ãŸãŸæŸçœ®ãããŠããç¶æ
ã§ãã
Codex ã埩垰ããçŽåŸã5ã€ã® launchd ã¬ãŒã³ãäžæã«èµ·åããŸãããåã¬ãŒã³ã®ããã¯ã¢ããã¹ã¯ãªãã㯠done/ é
äžã® JSON ã ls ããã㯠glob ã§ååŸããŠåŠçãå§ããŸããæä»å¶åŸ¡ããªãã®ã§ãåã JSON ãè€æ°ããã»ã¹ãåæã«èŠã«è¡ããçµæãšã㊠funnel-pm ã¬ãŒã³ã¯åäž JSON ã 1åå·®ã§4ååŠçããŸããã8/11ä»¶ã2é£å°ä»¥äžãšããæ°åã瀺ãããã«ãããã¯å¶çºçãªäºæ
ã§ã¯ãªãèšèšäžã®å¿
ç¶ã§ãã
launchd ã¯ããã¡ã€ã«ã®åãåãããç¥ããªã
com.lily.codex-note-funnel.plist ã® StartCalendarInterval ãèŠããšã10:40ãš16:40ã®2ãšã³ããªãèšå®ãããŠããŸãã
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>10</integer>
<key>Minute</key>
<integer>40</integer>
</dict>
<dict>
<key>Hour</key>
<integer>16</integer>
<key>Minute</key>
<integer>40</integer>
</dict>
</array>
launchd ã¯ãã®ã¹ã±ãžã¥ãŒã«ã«åŸã£ãŠ run-codex-funnel.sh ãåŒã³åºãã ãã§ãããååã®åŠçãçµãã£ãããããä»ã®ã¬ãŒã³ãåããã¡ã€ã«ãåŠçäžããããlaunchd ã¯äžåé¢ç¥ããŸãããããã¯èšèšéãã®æ£ããåäœã§ããlaunchd ã¯ã¹ã±ãžã¥ãŒã©ãŒã§ãã£ãŠããã¥ãŒããã¯ã¹ã§ã¯ãããŸããããã¡ã€ã«ãã¥ãŒã®æä»å¶åŸ¡ã¯ã¢ããªã±ãŒã·ã§ã³å±€ã®è²¬ä»»ãã€ãŸãããã¯ã¢ããã¹ã¯ãªããèªèº«ãå®è£
ãã¹ãæ©èœã§ãã
èç©ïŒäžæåŸ©åž°ãç«¶åãé¡åšåããã
éåžžé転ã§ã¯ãåã¬ãŒã³ã®ã¿ã€ãã³ã°ã埮åŠã«ãºã¬ãŠããããšã§ç«¶åãèµ·ãã«ããç¶æ ãèªç¶ã«ä¿ãããŠããŸããåŠç察象㮠JSON ã1æ¬ãã€é 次远å ããããããè€æ°ã¬ãŒã³ãåããã¡ã€ã«ãåæã«èŠãæ©äŒãå°ãªãã
ãšããã Codex ã® usage_limit_exceeded ã®ãããªé·æé忢ãå
¥ããšç¶æ³ãäžå€ããŸãã忢æéäžã«57æ¬ã® JSON ãæºãŸã£ãç¶æ
ã§å
šã¬ãŒã³ãäžæèµ·åããã
éåžžé転:
ã¬ãŒã³A â job_001 åŠç â å®äº
ã¬ãŒã³B â job_002 åŠç â å®äº ïŒã¿ã€ãã³ã°ããºã¬ãŠããç«¶åããªãïŒ
Codex 埩垰çŽåŸ:
ã¬ãŒã³A ââ
ã¬ãŒã³B ââ€âââ job_001 ãåæååŸ â 4é£å°
funnel-pm ââ
ïŒ57æ¬ãæºãŸã£ãŠãããå
šã¬ãŒã³ãå
é ãã¡ã€ã«ã«æ®ºå°ïŒ
ãééã調æŽããããšããäœæ¥å¯Ÿçã§ã¯ããã®ãäžææŸåºããã¿ãŒã³ã«å¯Ÿå¿ã§ããŸãããã©ãã ãééãçž®ããŠãã埩垰çŽåŸã®äžç¬ã¯å¿ ãå šã¬ãŒã³ãåæã«èµ·åããŸãã
ããã¯ãã¡ã€ã«ã§ã¯äžååãªçç±
ãlockãã¡ã€ã«ãäœãã°ããããšããçºæ³ã¯èªç¶ã§ãããããå®è£
ãããèŠããšåé¡ããããŸãã
# NG: check-then-act ã¯ååçã§ãªã
LOCKFILE=~/dev/note-autolike/.processing.lock
if [ ! -f "$LOCKFILE" ]; then
touch "$LOCKFILE"
process_file "$json"
rm "$LOCKFILE"
fi
if [ ! -f ] ã§ç¢ºèªããŠãã touch ãããŸã§ã®éã«ãå¥ããã»ã¹ãå²ã蟌ãããTime of Check to Time of UseïŒTOCTOUïŒãã®çªããããŸãã2ã€ã®ããã»ã¹ãåæã«ããã¡ã€ã«ãªããã確èªããŠäž¡æ¹ã touch ãå®è¡ããããããç«¶åã§ããé«è² è·ã»é«é »åºŠã®ç°å¢ã§ã¯ããã®çªãå®éã«éããŸãã
ããã«ãããã»ã¹ãåŠçéäžã§ã¯ã©ãã·ã¥ãããšããã¯ãã¡ã€ã«ãæ®ãç¶ããŸããæ¬¡å以éã®å šå®è¡ãããã¯ãèŠãŠå³éåºãããããåŠçãå®å šã«è©°ãŸããŸããã¯ã©ãã·ã¥åŸã®å埩æé ãå¥éçšæããå¿ èŠããããéçšã³ã¹ããäžãããŸãã
flock ã³ãã³ãã«ãããã¡ã€ã«ããã¯ãèããããŸãããlaunchd ãããã»ã¹ãæ°èŠçæãããã³ã«ãã¡ã€ã«ãã£ã¹ã¯ãªãã¿ãåŒãç¶ãããªããããã¬ãŒã³éã§ã®æä»ã«ã¯æ©èœããŸããã
autolike-plist-reconcile.sh ã瀺ããã¬ãŒãã®ææ³
æ¢åã® ~/.claude/scripts/autolike-plist-reconcile.sh ã«ãåãå顿èã§å®è£
ãããã¬ãŒãããããŸãããã㯠AUTOLIKE_TIMEOUT_SEC ã®å€ã plist ãš launchd ã®ããŒãæžã¿å€ã§ãºã¬ãŠããå Žåã« reload ããã¹ã¯ãªããã§ãããæ žå¿éšã¯æ¬¡ã®ã³ãŒãã§ãã
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
launchctl bootout "$D/$L" 2>/dev/null
ã¹ã¯ãªããåé ã®ã³ã¡ã³ãã«ãèµ°è¡äžã®ãžã§ãã bootout ãããšãã® run ã®ããããäžžããšæ¶ãããããå¿
ãåŸ
ã€ããšãããŸããlaunchctl list ã§ PID ãååŸãã-ïŒéå®è¡äžïŒã§ãªããã° bootout ãã¹ããããããåŠçã«å
¥ãåã«ãä»è
ãæ¢ã«åŠçäžããã確èªãããããªãã°å³åº§ã«éåºããââãã®çºæ³ãä»åã® mv ãã¿ãŒã³ãšåãã§ãã
ãã ã PID ãã§ãã¯ã«ã¯éçããããŸããã確èªããšãbootoutãã®éã«æ°ããããã»ã¹ãèµ·åããå¯èœæ§ããŒãã«ã¯ã§ããŸããããã®ã¹ã¯ãªãããåãæèã§ã¯èš±å®¹ç¯å²ã§ããããã¡ã€ã«ãã¥ãŒã®æä»ååŸã«ã¯äžååã§ãããã匷åãªã¢ãããã¯æäœãå¿ èŠã§ãã
å šäœã®æµã
ã¢ãŒããã¯ãã£å³ïŒç«¶åãã解決ãŸã§
Codex usage_limit_exceeded ãã埩垰
â
âŒ
~/dev/note-autolike/done/ ã« 57 æ¬ãæ»ç
ââââââââââââââââââââââââââââââââââââââââââ
â job_20260917_001.json â
â job_20260917_002.json â
â ... â
â job_20260917_057.json â
ââââââââââââââââââââââââââââââââââââââââââ
â
5 ã¬ãŒã³ã launchd ã«ããäžæèµ·å
ââââââââââââââââââââââââââââââââââââââââââ
â com.lily.codex-note-funnel (10:40) â
â com.lily.autolike.note1 â
â com.lily.autolike.note2 â
â com.lily.autolike.funnel-pm â 4é£å° â
â com.lily.autolike.social â
ââââââââââââââââââââââââââââââââââââââââââ
â
ãä¿®æ£åãåã¬ãŒã³ã done/ ã glob â å
é ãã¡ã€ã«ãååŸ
â æä»ãªã â å
šã¬ãŒã³ã job_001 ãåæã« read
â
funnel-pm ã 1åå·®ã§åäž JSON ã 4 ååŠç
âââââââââââââââââââââââââââââââââââââââââ
ãä¿®æ£åŸãmv ã«ããååçæææš©ååŸ
ââââââââââââââââââââââââââââââââââââââââââ
â mv done/job_001.json processing/ â
â ââ æå: èªããã»ã¹ã®ã¿ãåŠçãç¶ç¶ â
â ââ 倱æ: ä»ããã»ã¹ãååŸæžã¿ â exit 0â
ââââââââââââââââââââââââââââââââââââââââââ
â
ã1ãã¡ã€ã« = 1ããã»ã¹ã®ã¿åŠçããä¿èšŒããã
mv ãã¢ãããã¯ã§ããçç±
åäžãã¡ã€ã«ã·ã¹ãã äžã® mv ã¯ãPOSIX ã® rename(2) ã·ã¹ãã ã³ãŒã«ã1åã§å®è¡ããŸããã«ãŒãã«ã¬ãã«ã§ããã¡ã€ã«åã倿Žããããšããæäœãäžå¯åã«å®äºããããã2ã€ã®ããã»ã¹ãåæã« mv done/job_001.json processing/job_001.json ãå®è¡ããŠããã©ã¡ããäžæ¹ã ããæåããããäžæ¹ã¯å¿
ããšã©ãŒã«ãªããŸããã確èªããšãååŸãã®éã«çªãçãŸããŸããã
PID ãã§ãã¯ãš mv ã®æ ¹æ¬çãªéãã¯ããã«ãããŸããPID ãã§ãã¯ã¯ã確èªããšãåŠçéå§ããå¥ã®æäœã§ããmv ã¯ã確èªããšãååŸããäžã€ã®ã·ã¹ãã ã³ãŒã«ã®äžã§å®çµããŸãã
# 2ããã»ã¹ãåæã«å®è¡ããå Žåã®æå
# ããã»ã¹A: mv done/job_001.json processing/job_001.json â æåïŒçµäºã³ãŒã 0ïŒ
# ããã»ã¹B: mv done/job_001.json processing/job_001.json â 倱æïŒNo such file or directory, çµäºã³ãŒã 1ïŒ
pickup ã¹ã¯ãªãããžã®çµã¿èŸŒã¿
run-codex-funnel.sh ã®å
é ã«å·®ã蟌ãå®è£
ã§ãã
#!/bin/bash
set -uo pipefail
DONE_DIR=~/dev/note-autolike/done
PROCESSING_DIR=~/dev/note-autolike/processing
mkdir -p "$PROCESSING_DIR"
# done/ ããåŠç察象ã1æ¬éžã¶
TARGET=$(ls "$DONE_DIR"/*.json 2>/dev/null | head -1)
[ -z "$TARGET" ] && exit 0 # 察象ãªã â æ£åžžçµäº
BASENAME=$(basename "$TARGET")
# ã¢ãããã¯ã«æææš©ãååŸã倱æ = ä»ããã»ã¹ãååŸæžã¿
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null || exit 0
WORKING="$PROCESSING_DIR/$BASENAME"
# ããããå
ã¯èªããã»ã¹ã ããåŠçããæš©å©ãæã€
mv ã倱æããç¶æ³ã¯å®è³ª1ã€ã§ããä»ã®ããã»ã¹ãæ¢ã« mv æžã¿ã§ãã¡ã€ã«ãååšããªãããããç«¶åããç¶æ³ãã®ãã®ãªã®ã§ãexit 0 ã§å³éåºããã®ãæ£ããå¿çã§ãããšã©ãŒãã°ãåºãå¿
èŠãããããŸããã
ã¯ãã¹ããã€ã¹ã® mvïŒå¥ããŠã³ããã€ã³ãããŸããå ŽåïŒã¯ã¢ãããã¯ã§ãªãããæ³šæãå¿
èŠã§ãããä»åã®æ§æã§ã¯ done/ ãš processing/ ã¯ã©ã¡ãã ~/dev/note-autolike/ é
äžã«ãããåäžãã¡ã€ã«ã·ã¹ãã ãä¿èšŒãããŠããŸãã
com.lily.codex-note-funnel.plist ãšã®æ¥å
com.lily.codex-note-funnel.plist ã® ProgramArguments ã¯æ¬¡ã®æ§æã«ãªã£ãŠããŸãã
<key>ProgramArguments</key>
<array>
<string>~/.claude/scripts/claude-quota-guard.py</string>
<string>--job</string>
<string>com.lily.codex-note-funnel</string>
<string>--priority</string>
<string>--</string>
<string>~/dev/note-autolike/run-codex-funnel.sh</string>
</array>
claude-quota-guard.py ãã¯ã©ãŒã¿å¶åŸ¡ã®ã©ãããŒãšããŠå
è¡ããæ ãããã° run-codex-funnel.sh ãèµ·åããŸããpickup ããžãã¯ã¯ run-codex-funnel.sh ã®äžã«åãŸã£ãŠãããããã¹ã¯ãªããã®å
é æ°è¡ã« mv ã«ããæææš©ååŸãå·®ã蟌ãã ãã§å¯Ÿå¿ãå®çµããŸããplist ã®ä¿®æ£ã¯äžèŠã§ãã
launchd ã¯ä»åŸã10:40ãš16:40ã« run-codex-funnel.sh ãåŒã³åºããŸããCodex ã® usage_limit_exceeded ãã埩垰ããçŽåŸã«57æ¬ã® JSON ãæºãŸã£ãŠããŠããrun-codex-funnel.sh ãèµ·åãããã³ã«ããŸã mv ã§1æ¬ãååŸãã倱æãããå³éåºããããšããåäœãç¹°ãè¿ããŸããfunnel-pm ã¬ãŒã³ãå«ã5ã€ã®ããã»ã¹ãåæã«èµ°ã£ãŠããåãã¡ã€ã«ãåŠçããããã»ã¹ã¯1ã€ã ãã«çµãããŸãã
å®è£ ã®è©³çް
ã¹ã¯ãªããå šäœåïŒé²åŸ¡ã¬ã€ã€ãŒãéããé çª
ååã§ç€ºããã®ã¯ mv ã®äžè¡ã ãã§ããããå®éã«çšŒåãããã«ã¯ååŸã®ã¬ã€ã€ãŒãå¿
èŠã§ããcom.lily.codex-note-funnel.plist ã® ProgramArguments ãèŠããšãclaude-quota-guard.py ãå
è¡ã©ãããŒãšããŠåãããã®åŸ run-codex-funnel.sh ãåŒã°ããŸããã¹ã¯ãªããã®å
šäœåã¯ãããªããŸãã
#!/bin/bash
# launchd ç°å¢ã® PATH ã¯æå°éã®ããå
é ã§äžæžããã
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
export PATH
set -uo pipefail
DONE_DIR=~/dev/note-autolike/done
PROCESSING_DIR=~/dev/note-autolike/processing
PROCESSED_DIR=~/dev/note-autolike/processed
LOG=~/dev/note-autolike/logs/codex-funnel.log
mkdir -p "$PROCESSING_DIR" "$PROCESSED_DIR"
# èµ·åæãªã«ããª: ååã¯ã©ãã·ã¥ã§ processing/ ã«æ®ã£ããã¡ã€ã«ã done/ ãžæ»ã
for stale in "$PROCESSING_DIR"/*.json; do
[ -f "$stale" ] || break
age=$(( $(date +%s) - $(stat -f %m "$stale") ))
if [ "$age" -gt 3600 ]; then
mv "$stale" "$DONE_DIR/" 2>/dev/null
echo "[$(date '+%F %T')] recovered stale: $(basename "$stale")" >> "$LOG"
fi
done
# done/ ããåŠç察象ã1æ¬éžã¶
TARGET=$(ls "$DONE_DIR"/*.json 2>/dev/null | head -1)
[ -z "$TARGET" ] && exit 0
BASENAME=$(basename "$TARGET")
# âââ ãããæ žå¿ âââââââââââââââââââââââââââââââââââââââââ
# ã¢ãããã¯ã«æææš©ãååŸã倱æ = ä»ããã»ã¹ãååŸæžã¿ â å³éåº
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null || exit 0
# ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
WORKING="$PROCESSING_DIR/$BASENAME"
# ã¯ã©ãã·ã¥æã« processing/ ãžãã¡ã€ã«ãæ®ãã®ãé²ã EXIT ãã©ãã
# æ£åžžå®äºæã¯ processed/ ãžç§»åãç°åžžæã¯ done/ ãžå·®ãæ»ã
_cleanup() {
local code=$?
if [ $code -eq 0 ]; then
mv "$WORKING" "$PROCESSED_DIR/$BASENAME" 2>/dev/null
else
mv "$WORKING" "$DONE_DIR/$BASENAME" 2>/dev/null
echo "[$(date '+%F %T')] ERROR exit=$code, returned: $BASENAME" >> "$LOG"
fi
}
trap '_cleanup' EXIT
# ââ ããããå
ã¯èªããã»ã¹ã ãã $WORKING ãåŠçãã ââ
echo "[$(date '+%F %T')] processing: $BASENAME" >> "$LOG"
# ... æ¬äœåŠç ...
ãªã PATH ãå é ã§äžæžãããã®ã
com.lily.codex-note-funnel.plist ã® EnvironmentVariables ãèŠããšãããŒã¯ååšããŸããäžèº«ã¯ç©ºã® <dict/> ã§ãã
<key>EnvironmentVariables</key>
<dict>
</dict>
launchd ãããã»ã¹ãèµ·åãããšãã® PATH 㯠/usr/bin:/bin:/usr/sbin:/sbin ãšããæå°ã»ããã ãã§ããã¿ãŒããã«ã§ echo $PATH ãããš /usr/local/bin ãå«ãŸããŠããŸããããã㯠.zshrc ã nvm ã®åæåã¹ã¯ãªããã远å ããŠãããã®ã§ããlaunchd ã¯ãã®ãããªã·ã§ã«åæåãå®è¡ããŸãããjqã»nodeã»python3 ãªã© /usr/local/bin ã«å
¥ã£ãŠããããŒã«ã¯ãplist ããèµ·åããã¹ã¯ãªããã§ã¯ãã³ãã³ããèŠã€ãããªãããšãã圢ã§ãµã€ã¬ã³ãã«å€±æããŸãã解決ç㯠plist ã® EnvironmentVariables ã«æžãããã¹ã¯ãªããå
é ã§ããŒãã³ãŒããããã®äºæã§ãã åŸè
ã®ã»ããã¹ã¯ãªããåäœã§åäœç¢ºèªã§ããŠäŸ¿å©ãªã®ã§ãç§ã¯å
é ã§ export PATH ããæ¹éã«çµ±äžããŠããŸãã
set -uo pipefail ãš || exit 0 ã®çµã¿åãããå¿
é ãªçç±
set -uo pipefail ãæå¹ãªãšããã³ãã³ããéãŒãçµäºã³ãŒããè¿ããšã¹ã¯ãªããå
šäœãããã§äžæãããŸããmv ã¯å¯Ÿè±¡ãã¡ã€ã«ãååšããªãå Žåã«çµäºã³ãŒã 1 ãè¿ããŸããããã¯ãä»ããã»ã¹ãååŸæžã¿ããšããæ£åžžãªç«¶åã·ããªãªãšå®å
šã«äžèŽããŸãã
# NG: set -uo pipefail ç°å¢ã§ã¯ mv 倱æã§ã¹ã¯ãªãããéãŒãçµäºãã
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null
# OK: 倱æãæç€ºçã«ãæ£åžžãªéåºãã«å€æãã
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null || exit 0
2>/dev/null ã察ã«ãªã£ãŠå¿
èŠã§ãã|| exit 0 ã ãã§ã¯ stderr ã« "No such file or directory" ãåºåãããŸããcom.lily.codex-note-funnel.plist ã® StandardErrorPath 㯠~/dev/note-autolike/logs/codex-funnel.error.log ã«åããŠãããããç«¶åã®ãã³ã«æ¬æ¥ãšã©ãŒã§ãªãè¡ããšã©ãŒãã°ã«ç©ãŸããŸãããã°ãç£èŠãããšãããšã©ãŒãã°ãå¢ããŠããããšããã¢ã©ãŒãã誀çºç«ããåå ã«ãªããŸãã
autolike-plist-reconcile.sh ã瀺ãããäºéãã§ãã¯ãã®æžãæ¹
autolike-plist-reconcile.sh ã® PID ãã§ãã¯éšåã«ã¯ãèŠèœãšããã¡ãªçްããããããŸãã
pid=$(launchctl list | awk -v l="$L" '$3==l{print $1}')
if [ -n "$pid" ] && [ "$pid" != "-" ]; then
launchctl list ãè¿ãæåã®ã«ã©ã 㯠PID ã - ã§ãã- ã¯ãžã§ããç»é²ãããŠãããçŸåšå®è¡äžã§ã¯ãªãããšãæå³ããŸãã[ -n "$pid" ] ã ãã§ã¯ pid ã - ã®ãšãããPIDããããšå€å®ãããŠããŸããŸããã ãã [ "$pid" != "-" ] ã AND ã§ç¹ãã§ããŸãã
ãã®ã倿°ã空ã§ãªãããšããšãæå³ã®ããå€ãã©ããããåããŠãã§ãã¯ããæžãæ¹ã¯ãmv ãã¿ãŒã³ã«ãå¿çšã§ããŸããmv ã®çµäºã³ãŒãã ããèŠãã®ã§ãªããWORKING ãã¡ã€ã«ãå®éã«ååšãããã確èªããé²åŸ¡å±€ãå ãããå Žåã¯ããæžããŸãã
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null || exit 0
# 念ã®ãã: mv ãæåããã¯ããªã®ã«ãã¡ã€ã«ããªãïŒåäžFSã§ãªãçïŒ
[ -f "$WORKING" ] || { echo "FATAL: mv succeeded but file missing" >> "$LOG"; exit 1; }
ãã®è¿œå ãã§ãã¯ã¯éåžžã¯å°éããŸããããã¯ãã¹ããã€ã¹ã® mv ã誀ã£ãŠå®è¡ããå Žåã«çç¶ãæç€ºããŠãããŸããset -uo pipefail ãæå¹ãªç°å¢ã§é»ã£ãŠèœã¡ãããããã°ã«åå ãæ®ãã»ããåŸã®ãããã°ãå§åçã«éãã§ãã
èµ·åæãªã«ããªã®èšèšææ³
ã¹ã¯ãªããã®å é ã«æžããã1æé以äžåã® processing/ ãã¡ã€ã«ã done/ ãžæ»ããåŠçã¯ããããã¬ã¿ãŒå¯Ÿçã§ãã
age=$(( $(date +%s) - $(stat -f %m "$stale") ))
if [ "$age" -gt 3600 ]; then
mv "$stale" "$DONE_DIR/" 2>/dev/null
fi
mv ã§æææš©ãååŸããåŸã«ããã»ã¹ãã¯ã©ãã·ã¥ãããšããã¡ã€ã«ã¯ processing/ ã«æ®ããŸããEXIT ãã©ããã§ done/ ãžå·®ãæ»ãèšèšã«ããŠãããŸãããã·ã°ãã« 9 ã§åŒ·å¶çµäºãããå Žåãªã©ã¯ãã©ãããåããŸãããç¿æã« done/ ãèŠããŠã空ããããåŠçã¯å¢ããŠããªãââãšããçç¶ã¯ãã®ãã¿ãŒã³ãåå ã§ãããªã«ããªãèµ·åæã«å®è¡ããããšã§ã次åã® launchd åŒã³åºãã§èªåçã«åãã¥ãŒã«å
¥ããŸããéŸå€ã 3600 ç§ïŒ1æéïŒã«ããŠããã®ã¯ãæ£åžžãªé·æéåŠçã誀ã£ãŠå·®ãæ»ããªãããã§ããcom.lily.codex-note-funnel.plist ã® StartCalendarInterval ã 10:40 ãš 16:40 ã®2ãšã³ããªãªã®ã§ãæå€§ã¹ã±ãžã¥ãŒã«ééã¯6æéã§ãã1æéã¯ãã®ç¯å²å
ã§ååãªå®å
šããŒãžã³ã§ãã
ç§ãè©°ãŸã£ã話
è©°ãŸã1ïŒç«¶åãæ¶ããã®ã«ãšã©ãŒãã°ãå¢ãç¶ãã
mv || exit 0 ãå®è£
ããç¿æ¥ãcodex-funnel.error.log ãèŠããš1ä»¶ã ã£ãè¡ã32ä»¶ã«ãªã£ãŠããŸãããåŠçä»¶æ°ã¯æ£åžžã«å¢ããŠããŸãã4é£å°ã¯æ¶ããããªã®ã«ãšã©ãŒãã°ãå¢ããã
æåã¯ãã©ããã§äŸå€ãåºãŠããã®ã§ã¯ããšæ¬äœåŠçã®ãããã°ãå§ããŠããŸããŸããã30åã»ã©è¿œããããŠããæ°ã¥ããã®ã 2>/dev/null ã®æ¬ èœã§ããæåã«æžããã³ãŒãã¯ããã§ããã
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" || exit 0
|| ã¯çµäºã³ãŒãã®ãã³ããªã³ã°ã ãã§ããstderr ãžã®åºåã¯æ¢ãŸããŸãããlaunchd 㯠stderr ã StandardErrorPath ã«æžãç¶ããŸããçµæãšããŠç«¶åã®ç¬éã®ãã³ã«ãNo such file or directoryããèç©ãããŸããã
æ£ããã¯ããã§ãã
mv "$TARGET" "$PROCESSING_DIR/$BASENAME" 2>/dev/null || exit 0
ãã®2ã€ãåžžã«ã»ããã§æžãç¿æ £ãæãŠãŠããªãã£ãã®ãåå ã§ããããšã©ãŒãã°ãå¢ããïŒæ¬äœã«ãã°ãããããšããå å ¥èŠ³ã§ãããã°ã®æ¹åã誀ããŸããããšã©ãŒãã°ãå¢ãããšãã¯ãŸããæ£åžžãªå€±æãé»ãããŠããããã確èªããããšããé åºãå ã«å®åã«ããŠããã¹ãã§ããã
è©°ãŸã2ïŒprocessing/ ã«ãã¡ã€ã«ãæºãŸãç¶ããdone/ ãæ°žé ã«ç©ºã«èŠãã
EXIT ãã©ãããå®è£
ããåã®çã§èµ·ããåé¡ã§ããå€äžã« Codex ãã¯ã©ãã·ã¥ããã±ãŒã¹ãæ°åç¶ããç¿æç¢ºèªãããš done/ ã¯ç©ºãªã®ã«åŠçãåã£ãŠããŸããã§ããã
çç¶ã远ããš processing/ ã«5æ¬ã® JSON ãèç©ãããŠããŸãããã¹ã¯ãªããã¯èµ·åãããã³ã« done/ ã確èªã㊠TARGET ã空ãªã®ã§ exit 0 ãããããããåŠçãã¹ããã¡ã€ã«ã㯠processing/ ã«è©°ãŸã£ãŠããŸãã
done/ â ç©ºïŒæ¬¡ã®ä»äºã¯ããã«æ¥ãïŒ
processing/ â job_001.json, job_002.json ... â ãŸã³ã
processed/ â å®äºæžã¿
ãã®ç¶æ
ã¯å®å®³ãäºéã§ããäžã€ç®ã¯åŠçã®åæ¢ãäºã€ç®ã¯ãè©°ãŸã£ãŠããããšãå€ããèŠããªããããšãdone/ ã空ãªã®ã§èªååãæ£åžžã«èŠãããããã done/ ã«æ°ãããã¡ã€ã«ãæ¥ãŠã processing/ ã®ãŸã³ãã«ã¯èª°ãè§ŠããŸããã
çŽãæ¹ã¯2ã€ãçµã¿åãããŸãããEXIT ãã©ããã§å·®ãæ»ãããšãšãèµ·åæã«å€ã processing/ ãã¡ã€ã«ããªã«ããªããããšã§ããã©ã¡ããçæ¹ã§ã¯äžååã§ããã©ãããåããªã匷å¶çµäºã«å¯Ÿå¿ããã«ã¯ã¹ã¿ãŒãã¢ãããªã«ããªãå¿
èŠã§ããå®è£
åŸãprocessing/ ãã£ã¬ã¯ããªã宿çã« ls -la ããŠãã¡ã€ã«ãæ»çããŠããªããã確èªããã ãã§ããã®çš®ã®è©°ãŸãã¯ç¿æã«ã¯èªåè§£æ¶ãããããã«ãªããŸããã
è©°ãŸã3ïŒã¿ãŒããã«ã§èµ°ããããšæåãlaunchd ããèµ·åãããšç¡èšã§äœãããªã
æãæéãæº¶ããã倱æã§ããrun-codex-funnel.sh ãã¿ãŒããã«ã§çŽæ¥å®è¡ãããšæ£åžžã« JSON ãåŠçããŸããlaunchd çµç±ã ãš codex-funnel.log ã«ãprocessing: ...ããäžåçŸããŸããããšã©ãŒãã°ã空ã§ããã¹ã¯ãªããã¯èµ·åããŠããããäœãããŠããªãã
åå ã®ç¹å®ã«äœ¿ã£ãã®ã¯ãlaunchd ãèµ·åããã·ã§ã«ãã§ããã ãå¿ å®ã«åçŸããæ¹æ³ã§ãã
env -i HOME="$HOME" PATH=/usr/bin:/bin:/usr/sbin:/sbin bash -l ~/dev/note-autolike/run-codex-funnel.sh
env -i ã§ç°å¢å€æ°ãæå°åããŠèµ°ããããšãéäžã§ jq: command not found ãåºãŸãããæ¬äœåŠçã®äžã§ JSON ãã needs_imagegen_thumbnail ãã©ã°ãèªã¿åºãã®ã« jq ã䜿ã£ãŠããã®ã§ãããjq 㯠/usr/local/bin/jq ã«ã€ã³ã¹ããŒã«ãããŠããŸãããset -uo pipefail ãæå¹ãªã®ã§ jq ãèŠã€ãããªãæç¹ã§ã¹ã¯ãªãããéãŒãçµäºããŸããçµäºã³ãŒã㯠codex-funnel.error.log ã«æžãããã¯ãã§ããããšã©ãŒã¡ãã»ãŒãžãã®ãã®ã stderr ã«åºãŠããªãã£ãïŒjq ããªãã®ã§ãšã©ãŒããã¹ããåºãªãïŒããããã°ã空ã§ããã
com.lily.codex-note-funnel.plist ã® EnvironmentVariables ã空ã§ããããšãèªãã§ããã°å
ã«æ°ã¥ããåé¡ã§ãããã¡ã€ã«ãå®éã«èªãåã«ããã¶ã PATH ã¯å€§äžå€«ã ããããšããæã蟌ã¿ã§åäœç¢ºèªãçããã®ãåå ã§ããã以æ¥ãlaunchd çµç±ã®ã¹ã¯ãªããã¯ãã¹ãŠåé ã« PATH= ãæžãã絶察ã«ãŒã«ã«ããŠããŸãã
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
export PATH
ãã®äžè¡ãå ¥ã£ãŠãããã©ããããã³ãŒãã¬ãã¥ãŒã®æåã«ç¢ºèªããããã«ãªããŸããã
è©°ãŸã4ïŒls *.json ãããã¡ã€ã«ãªããã§çµäºã³ãŒã 2 ãè¿ããŠã¹ã¯ãªãããèœã¡ã
done/ ã空ã®ãšããls "$DONE_DIR"/*.json ã¯ã·ã§ã«ã®ã°ããå±éã«å€±æããçµäºã³ãŒã 2 ãè¿ããŸããset -uo pipefail ç°å¢ã§ã¯ããããã¹ã¯ãªããã®ç°åžžçµäºãåŒãèµ·ãããŸãã
# NG: done/ ã空ã®ãšã ls ã exit 2 â pipefail ã§ã¹ã¯ãªããå
šäœãèœã¡ã
TARGET=$(ls "$DONE_DIR"/*.json | head -1)
# OK: 2>/dev/null ã§ãšã©ãŒãæšãŠã倿°ã空ãã©ããã§å€æãã
TARGET=$(ls "$DONE_DIR"/*.json 2>/dev/null | head -1)
[ -z "$TARGET" ] && exit 0
ãã®åé¡ã¯ done/ ã空ã«ãªãã®ããç°åžžããªèšèšãªãã°èµ·ããŸãããä»åã®æ§æã¯ãéåžžé転ã§ã¯ done/ ãç©ºã®æéã®ã»ããé·ããèšèšãªã®ã§ãç©ºãæ£åžžãã¹ã§ãããç©ºãæ£åžžãšããŠæ±ããããã«ã¯ãã³ãã³ãã®å€±æãæç€ºçã«åžåããè¡ãå¿
èŠã§ãã
autolike-plist-reconcile.sh ã®æžãæ¹ãèŠããšãcontinue ã䜿ã£ãŠã«ãŒããæããå Žé¢ã3åãããŸãã
want=$($PB -c "Print :EnvironmentVariables:AUTOLIKE_TIMEOUT_SEC" "$P" 2>/dev/null) || continue
have=$(launchctl print "$D/$L" 2>/dev/null | ...) | head -1)
[ -n "$have" ] || continue
[ "$want" = "$have" ] && continue
|| continue ãéæã«å
¥ã£ãŠããŸããããã®ã¬ãŒã³ã®æ
å ±ãåããªããã°ã¹ãããããšãã倿ãããšã©ãŒãæ¢ããã«æ¬¡ã«é²ãåœ¢ã§æžããŠããŸãããã®ã倱æã次ã®åŠçãžã®æç€ºã«å€æãããçºæ³ããä»åã® mv ... || exit 0 ãšåãææ³ã§ãããšã©ãŒã¯ãã°ã«æ®ããã®ã§ã¯ãªããæ¬¡ã®ã¢ã¯ã·ã§ã³ãžã®ã·ã°ãã«ãšããŠäœ¿ããã®ââãã®èŠç¹ã«åãæ¿ããŠãããã¹ã¯ãªããã®èŠéããæ Œæ®µã«ãããªããŸããã
è©°ãŸã5ïŒfunnel-pm ã4é£å°ããŠãããšæ°ã¥ãã®ã1æ¥åŸã ã£ã
æåŸã®ãè©°ãŸããã¯æè¡ãšãããã芳枬ã®åé¡ã§ãã9æ17æ¥ã«4é£å°ãèµ·ããŠããããšã«æ°ã¥ããã®ã¯ç¿09:18ã®05:20ã« processed/ ã®ä»¶æ°ãæ°ãããšãã§ãããã°ãèŠããš1åå·®ã§åãJSONã®ãšã³ããªã4ã€äžŠãã§ããŸããã
åé¡ãèµ·ããŠãã17æéãç§ã¯äœãç¥ããŸããã§ããããã°ã¯ ~/dev/note-autolike/logs/codex-funnel.log ã«ç©ãŸããŠããŸããã誰ãããããªã¢ã«ã¿ã€ã ã§ç£èŠããŠããŸããããåŠçãéè€ããããšããäºå®ã¯ãprocessed/ ã®ãã¡ã€ã«åãéè€ããŠããããšããããæ€åºã§ããŸããã§ããã
éè€æ€åºã®ã¯ã³ã©ã€ããŒã¯ä»ã§ã¯æ¯æã®ç¢ºèªã«äœ¿ã£ãŠããŸãã
ls ~/dev/note-autolike/processed/ | sed 's/_[0-9]*$//' | sort | uniq -d
ãã¡ã€ã«åã®ã¿ã€ã ã¹ã¿ã³ãéšåãé€ããŠããŒã¹åã§éè€ãã§ãã¯ããŸãããããäœããè¿ãããããã®æ¥ã®åŠçã«äºéå®è¡ããã£ãããšã«ãªããŸãã
éè€ããŒãã«ããæ§é æ¹åïŒmv ãã¿ãŒã³ïŒãšããããšã¯ç¬ç«ããŠãéè€ããã£ãããšã«æ°ã¥ããç£èŠããæã€ããšã¯ãå¥ã®ã¬ã€ã€ãŒã®åé¡ã§ããå£ããªãèšèšãäœãã€ã€ãå£ãããšãã«ç¿æ¥ã§ã¯ãªãåœæ¥äžã«æ°ã¥ããä»çµã¿ãæã€ââãã®äºæ®µæ§ãããèªååãå®åçã«ã€ãªãç°å¢ã§ã¯æ¬ ãããŸããã
ã€ãŸãããã€ã³ã
ç°å¢ã»æ§æç³»
-
launchd 㯠PATH ãåŒãç¶ããªãã
EnvironmentVariablesãããã¯ã空ã®ãŸãŸåãããšè©°ãŸããcom.lily.codex-note-funnel.plistã®EnvironmentVariablesã¯ç©ºã«ãªã£ãŠãããlaunchd èµ·åæã®å®å¹ PATH ã¯/usr/bin:/bin:/usr/sbin:/sbinã ãã§ããnvm ã§ã€ã³ã¹ããŒã«ãã codex ã¯ãã® PATH ã«ã¯ååšããŸãããrun-codex-funnel.shãset -uo pipefailãæã£ãŠããŠãããã³ãã³ããèŠã€ãããªããç¶æ³ã§ã¯-uã¯åå¿ãé ããã±ãŒã¹ããããŸãïŒwhich codexã空æåãè¿ãïŒã蚺æã¯launchctl start com.lily.codex-note-funnelã§æåèµ·åããŠãã°ã確èªããã®ãæéã§ãã -
Nice: 10ãšLowPriorityIO: trueã¯ããŒã¹ãåŠçãšçžåãããplist ã®äž¡èšå®ã¯ã·ã¹ãã é«è² è·æã«ã¹ã¯ãªããã®å®è¡ãåŸåãã«ããŸããCodex 埩垰åŸã«57æ¬ã® JSON ãäžæ°ã«æ¶åããããšããNice: 10ã¯ãä»ã®ãŠãŒã¶ãŒæäœãž CPU ãè²ããèšå®ãªã®ã§åŠçãé ããŸãããããã¯ã°ã©ãŠã³ãã§éãã«åãããšã埩垰åŸã«å šåã§æ¶åãããã¯çžåããŸããã©ã¡ããåªå ãããæå³ã決ããã³ã¡ã³ãã«æžããŠãã ãããçŸç¶ã® plist ã¯åè ãéžãã§ããŸãã -
ThrottleIntervalã®ããã©ã«ã㯠launchd ããŒãžã§ã³äŸåãéãŒãçµäºãç©ã¿éãªããšStartCalendarIntervalã®å®å»èµ·åãé£ã¶ãp2 ã®å€±æ3ïŒexit 1ã§ã¹ããããªã³ã°ïŒãšçŽçµãã話ã§ããã远èšããŠãããšã10:40 ãã¹ããããªã³ã°ã§é£ãã å Žåãæ¬¡ã®å®å»ã¯ 16:40 ãŸã§6æéåŸã§ããcodex-funnel.error.logïŒStandardErrorPathïŒã«äœãåãããŠããªãæ¥ãé¡ã£ãŠç¢ºèªããã®ã確å®ãªèšºææ³ã§ããexit 0ã培åºããã°ããã©ã«ãã®ThrottleIntervalèšå®ã§åé¡ãããŸãããããããã©ã«ãä»»ãããªããã®æå³ã plist ã®ã³ã¡ã³ããããã¥ã¡ã³ãã«æ®ããŠãã ããã -
claimed/ãã£ã¬ã¯ããªäžåšã®mv倱æãšãç«¶ååé¿ã®mv倱æã¯åãexit 0ãè¿ããp2 ã®å€±æ5ã§è§Šããåé¡ã§ãããæ ¹æ¬ã«ããèšèšäžã®ãã¬ãŒããªããšããŠæ¹ããŠæžããŸãããæ£åžžãªç«¶ååé¿ããšãç°åžžãªç°å¢äžåããåãçµäºã³ãŒãã«ããããšã§ãlaunchd ã® Throttle ãé²ããŸãããåé¡ã®çºèŠãé ããŸãããã®æ²é»ã®ä»£åãæ¯æã以äžãå€éšç£èŠãå¿ é ã§ãã
mv ã¯ã¬ãŒã ãã¿ãŒã³åºæã®ã¯ã
-
ã¯ãã¹ããã€ã¹
mvã¯ã¢ãããã¯ã§ãªããPOSIX rename(2)ãã«ãŒãã«ã§çŽååãããã®ã¯ãåäžãã¡ã€ã«ã·ã¹ãã äžãã ãã§ããqueue/ãšclaimed/ãå¥ããŠã³ããã€ã³ãïŒäŸïŒNFSãDockerããªã¥ãŒã ãå€ä»ããã©ã€ãïŒã«ãŸããããšãå éšã§ copy + delete ã«åè§£ãã TOCTOU ã®çªãçãŸããŸããä»åã®æ§æã¯äž¡ãã£ã¬ã¯ããªãšã~/dev/note-autolike/é äžãªã®ã§åé¡ãããŸãããããã£ã¬ã¯ããªæ§æã倿Žãããšãã¯df -hã§åäžãã¡ã€ã«ã·ã¹ãã ã確èªããŠãã ããã -
ãã¡ã€ã«åã®ãœãŒãé ãåç§äœæã§äžå®å®ã«ãªãã
ls -1 "$QUEUE_DIR"/*.json | sort | head -1ã®èŸæžé ãœãŒãã¯ããã¡ã€ã«åã®ã¿ã€ã ã¹ã¿ã³ãéšåãç°ãªãããšãåæã«ããŠããŸãããããæå ¥ã§1ç§ä»¥å ã«è€æ°ãã¡ã€ã«ãäœæããããšååãã¬ãã£ãã¯ã¹ãè¡çªãã5ã¬ãŒã³ãç°ãªããã¡ã€ã«ããå é ããšããŠååŸããŸãããã®çµæãã¯ã¬ãŒã ç«¶åãèµ·ããã«å šã¬ãŒã³ãåæåŠçããCodex ã»ãã·ã§ã³ã5æ¬åæèµ·åããŸããåé¿çã¯ãã¡ã€ã«åã«ãšããã¯ç§ + PID ãµãã£ãã¯ã¹ãå ¥ããããšïŒjob_$(date +%s)_$$_$(uuidgen | cut -d- -f1).jsonã -
claimed/ã®æ®çœ®ãã¡ã€ã«ãç¡é³ã®ãããã¬ã¿ãŒãã¥ãŒã«ãªããåŠçãéäžã§ã¯ã©ãã·ã¥ãããšããã¡ã€ã«ã¯claimed/ã«æ®ããŸããæ¬¡ã® launchd èµ·åã§ã¯queue/ãèŠãã®ã§claimed/ã®ãã¡ã€ã«ã¯æ°žé ã«ååŸãããŸãããå®éã«ãCodex 埩垰åŸã®2026幎9æ17æ¥ã«claimed/å ã§è€æ°æ¬ãæ¢ãŸã£ãŠããæ¥ããããŸãããclaimed/ã®ãã¡ã€ã«ã1æéä»¥äžæŽæ°ãªãã«ååšãããã¢ã©ãŒããåºãç£èŠãå ¥ããŠããã¯ããã®ç¶æ ã«æ°ã¥ããŸã§ã®ã¿ã€ã ã©ã°ããŒãã«ãªã£ãŠããŸãã -
claude-quota-guard.pyã fd ãç¶æ¿ããåé¡ã¯mvã¯ã¬ãŒã åŸãæ®ããcom.lily.codex-note-funnel.plistã®ProgramArgumentså é ã~/.claude/scripts/claude-quota-guard.pyã§ãããã®Pythonã©ãããŒãsubprocess.Popenã§run-codex-funnel.shãèµ·åããéãlaunchd ããã¯ãžã§ãããå®è¡äžãã«èŠããŸããautolike-plist-reconcile.shã® PID ãã§ãã¯ïŒlaunchctl list | awk -v l="$L" '$3==l{print $1}'ã§ PID ã-ã§ãªãããšã確èªïŒããcodex-funnel ã¯å®è¡äžããšã㊠reload ãèŠéãå€å®ã¯ããã®ãã©ãããŒãçåäžãã®ç¶æ ãæ£ããææããŸããmvã¯ã¬ãŒã ã§ç«¶åã¯æ¶ããŸããããã©ãããŒèµ·åãåæã«ãã PID ã®èªã¿æ¹ã¯æèããŠããå¿ èŠããããŸãã -
reconcile.shã®ã°ãããcodex-note-funnelã察象å€ã«ããŠãããautolike-plist-reconcile.shã®å¯Ÿè±¡ã¯com.lily.autolike.*.plistã§ããcom.lily.codex-note-funnel.plistã¯å¥ãã¡ã€ã«ã§ããã®ã°ããã«ã¯ãããããŸãããreconcile ãè¡ã timeout å€ã®èªå reload ã codex-funnel ã«ã¯å¹ããªãã®ã§ãcodex-funnel ã® timeout èšå®å€æŽæã¯æåã§launchctl bootout+bootstrapãå¿ èŠã§ããæå³éãã§ããã°ã³ã¡ã³ãã§æèšããŠãããšãå幎åŸã®èªåãè¿·ããŸããã
éçšã»ç£èŠç³»ã®ã¯ã
-
codex-funnel.logã®ããŒããŒã·ã§ã³æªèšå®ãplist ã®StandardOutPathã¯~/dev/note-autolike/logs/codex-funnel.logã«åããŠããŸããlaunchd ã¯ãã°ãããŒããŒã·ã§ã³ããŸããã1åãããæ°åè¡ã®ãã°ãç©ãŸãç¶ãããš1å¹Žã§æ°åMBã«ãªããŸããnewsyslogãlogrotateã§ã®æç€ºçãªããŒããŒã·ã§ã³ããŸãã¯ã¹ã¯ãªããå ã§ã®è¡æ°äžé管çãå¿ èŠã§ãã -
ããã°ãæŽæ°ãããªãããèªåæ€ç¥ããä»çµã¿ããªããšãé±åäœã§æ¢ãŸãã«æ°ã¥ããªãã10:40 ã®èµ·ååŸ15å以å ã«
claimed/ã®æŽæ°ã¿ã€ã ã¹ã¿ã³ããå€ãã£ãŠããªããã°ã¢ã©ãŒããé£ã°ãç£èŠãå¥éå ¥ããŠããŸããexit 0ã®èšèšã¯ launchd ãšã®çžæ§ãæå€§åããŸããããã®ä»£ããã«ãæ£åžžçµäºãšç¡é³é害ãåºå¥ã§ããªãããšããæ²é»ã®ãªã¹ã¯ãæã¡ãŸãããã®ç£èŠã¹ã¯ãªããã¯ãªãã·ã§ã³ã§ã¯ãªããmv ã¯ã¬ãŒã ãã¿ãŒã³ãšå¯Ÿã§å ¥ããã¹ããã®ã§ãã
ãã¹ããã©ã¯ãã£ã¹
èªåŸç°å¢ã6ã¶æãæå120äžåèŠæš¡ã§åããŠåºãŸã£ãã«ãŒã«ããå®ã³ãŒãã«æ ¹æ ã眮ããŠç€ºããŸãã
1. mv ã«ããæææš©ååŸãã¹ã¯ãªããã®å
é ã«çœ®ã
åŒæ°ããªããŒã·ã§ã³ã»ãã°åæåã»ãã£ã¬ã¯ããªç¢ºèªããåã« mv ãå©ããŸãããã¡ã€ã«ãåããªããã°åŸç¶ã®åŠçãäžåèµ°ãããå¿
èŠããããŸãããã¬ãŒã³ãããã® CPUã»ã¡ã¢ãªäœ¿çšãæå°ã«ãã€ã€ãç«¶å解決ãæéã§ãã°ããŸãã
TARGET=$(ls -1 "$QUEUE_DIR"/*.json 2>/dev/null | sort | head -1)
[ -z "$TARGET" ] && exit 0
BASENAME=$(basename "$TARGET")
mv "${QUEUE_DIR}/${BASENAME}" "${CLAIMED_DIR}/${BASENAME}" 2>/dev/null || exit 0
# ããããäžã¯èªããã»ã¹ã ããå®è¡ãã
2. ã¯ã¬ãŒã æåã¯ãã°ã«æ®ãã倱æïŒç«¶ååé¿ïŒã¯ç¡é³ã«ãã
ãæå = ãã°ãããç«¶ååé¿ = ç¡é³ãã«ãããš grep "claimed:" codex-funnel.log | wc -l ã仿¥ã®åŠçä»¶æ°ã«ãªããŸãããã£ã«ã¿äžèŠã®ã«ãŠã³ããã°ã¯å®æç£èŠã¹ã¯ãªããã®å®è£
ã³ã¹ãã倧å¹
ã«äžããŸããexit 0 ã« 2>/dev/null ãã»ããã«ããã®ã¯äžè²«ãããã¿ãŒã³ã§ãã
3. exit 0 ãè¿ãã®ã¯ãæ£åžžãªç«¶ååé¿ããšãqueue ã空ãã«éå®ãã
ããã£ã¬ã¯ããªãååšããªãããæš©éãšã©ãŒããäºæããªã mv ãšã©ãŒã㯠exit 1 ãè¿ããŠãã ãããç¡é³çµäºãå¢ãããšé害ã®çºèŠãé
ããŸããåæãããã€ã¹ã¯ãªããã§ç°å¢ãæŽããäžã§ãã¹ã¯ãªããæ¬äœã¯ãæ£åžžãªéåºã ãã exit 0ããšããåæã§æžããšä¿å®ã³ã¹ããäžãããŸãã
4. queue/ claimed/ done/ ã®3ãã£ã¬ã¯ããªã§ç¶æ
ãå¯èŠåãã
ãã£ã¬ã¯ããªèªäœãç¶æ
ã衚ããš ls ~/dev/note-autolike/claimed/ | wc -l ã§ãè©°ãŸã£ãŠãããã¡ã€ã«æ°ããäžçºã§ããããŸãããã©ã°ç®¡çã DB äžèŠã§ãls ã ããç£èŠããŒã«ã«ãªããŸãã
5. ãã¡ã€ã«åã«ãšããã¯ç§ + PID ãå ¥ããŠãœãŒãé ãå®å®ããã
FILENAME="job_$(date +%s)_$$_$(uuidgen | cut -d- -f1).json"
$$ ã¯çæããã»ã¹ã® PID ã§ããåç§ã«è€æ°ãã¡ã€ã«ãäœæãããŠã PID ãç°ãªãã°èŸæžé ã確å®ããŸãã5ã¬ãŒã³å
šå¡ãåããã¡ã€ã«ãå
é ãšããŠéžã¶ããšãã mv ã¯ã¬ãŒã ãã¿ãŒã³ã®åæãå®å®ããŸãã
6. reconcile.sh ã®ãèŠéãçç±ãèšé²ããããã¿ãŒã³ãå
šã¹ã¯ãªããã«çµ±äžãã
autolike-plist-reconcile.sh 18ã22è¡ç®ã¯ãå®è¡äžã®ãžã§ããèŠã€ãããšãçç±ããã°ã«æ®ã㊠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
ããªãäœãããªãã£ãããããã°ã«æ®ããã¿ãŒã³ã¯ãåŸããã®é害远跡ãåçã«æ¥œã«ããŸããmv ã¯ã¬ãŒã ã§ç«¶åãè§£æ¶ããåŸãããã®ç¿æ £ã¯ç¶æããŠãã ããã
7. ã¯ã©ãã·ã¥åŸã®å埩åŠçã¯ã¡ã€ã³ã¹ã¯ãªãããšåé¢ãã
claimed/ ã®æ®çœ®ãã¡ã€ã«ã queue/ ã«æ»ãåŠçãã¡ã€ã³ã¹ã¯ãªããã«çµã¿èŸŒããšã2ããã»ã¹ãåæã«ãæ®çœ®ãã¡ã€ã«ãæ»ã â åååŸã詊ã¿ãããšããç«¶åãåçºããŸããå埩ã¯äœé »åºŠã§èµ·åããç¬ç«ããç£èŠã¹ã¯ãªããã«å§ããèšèšãå®ã£ãŠãã ããã
8. ã¯ã¬ãŒã çŽåŸã«ãµã€ãã«ãŒãã¡ã€ã«ã§éå§æå»ãèšé²ãã
mv "${QUEUE_DIR}/${BASENAME}" "${CLAIMED_DIR}/${BASENAME}" 2>/dev/null || exit 0
echo "{\"claimed_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" \
> "${CLAIMED_DIR}/${BASENAME}.claim"
.claim ãµã€ãã«ãŒã«éå§æå»ãæžããšã1æé以äžçµéãã .claim ãæã€ãã¡ã€ã«ã queue/ ã«æ»ãããšããå埩ã¹ã¯ãªãããæžããŸããæ®çœ®ãã¡ã€ã«ã®è忀ç¥ã粟床ããã§ããŸãã
9. åæãããã€ã¹ã¯ãªãããã¹ãçã«ããŠåå®è¡å¯èœã«ãã
mkdir -p ~/dev/note-autolike/{queue,claimed,done,logs}
launchd ã® bootstrap ãåŒã¶åã«ãããå®è¡ããã¹ã¯ãªãããçšæããŠãã ããããã£ã¬ã¯ããªãååšããªãããšã§ mv ãç¡é³å€±æãããšãããp2 ã®å€±æ5ã®åçŸãæ§é çã«é²ããŸãã
10. ã¬ãŒã³éã®ãã°ãã¡ã€ã«ãåé¢ããè¡ã®æ··ãããé²ã
plist ã® StandardOutPath: codex-funnel.logãStandardErrorPath: codex-funnel.error.log ãšããèšå®ã¯ã¬ãŒã³ããšã«å¥ãã¡ã€ã«ãæããŠãããããã¯æ£ããã§ãã5ã¬ãŒã³ãåæèµ·åãã埩垰åŸã®ç¶æ³ã§å
±æãã°ãæ··ãããšãäœãèµ·ãããã®åçŸãäžå¯èœã«ãªããŸããã©ãã«åïŒcom.lily.codex-note-funnelïŒããã°ãã¡ã€ã«åã«å«ããã®ãæäœã©ã€ã³ã§ãã
11. ThrottleInterval ã®æåãæèããŠèšå®ã®æå³ãæç€ºãã
exit 0 ã培åºããã° launchd ã®ããã©ã«ã ThrottleInterval ã§åé¡ã¯èµ·ããŸããããã ããããã©ã«ãä»»ããã§ããããšãæèçã«éžæããplist ãéçšããã¥ã¡ã³ãã«ãã®çç±ãæžããŠãããŠãã ãããå幎åŸã«ãã©ãã«ãèµ·ãããšããèšå®ãå€ããŠããªãã®ã«ãªãïŒããšããæ··ä¹±ãé²ããŸãã
12. åäžãã¡ã€ã«ã·ã¹ãã ã df -h ã§ç¢ºèªããŠãã queue/ ãš claimed/ ãé
眮ãã
ã¢ãããã¯æ§ã®åæã厩ãæå€§ã®ãªã¹ã¯ã¯ã¯ãã¹ããã€ã¹ mv ã§ãããã£ã¬ã¯ããªã倿Žãããã³ã«ç¢ºèªããç¿æ
£ããããã€ã¹ã¯ãªããã«çµã¿èŸŒãã§ãã ããïŒ
queue_dev=$(df -P "$QUEUE_DIR" | awk 'NR==2{print $1}')
claimed_dev=$(df -P "$CLAIMED_DIR" | awk 'NR==2{print $1}')
[ "$queue_dev" = "$claimed_dev" ] || { echo "ERROR: cross-device mv"; exit 1; }
ãŸãšã
Codex ã® usage_limit_exceeded ãã埩垰ãã2026幎9æ17æ¥ã«èµ·ãããfunnel-pm ã¬ãŒã³ãåäž JSON ã1åå·®ã§4ååŠçãããåé¡ã¯ãèšèšäžã®å¿
ç¶ã§ããã57æ¬ã® JSON ãæºãŸã£ãç¶æ
ã§5ã¬ãŒã³ãäžæèµ·åããã°ãæä»å¶åŸ¡ãæããªã pickup ã¹ã¯ãªããã¯ç¢ºçã§ã¯ãªãæ§é çã«è¡çªããŸããlaunchd ã¯ã¹ã±ãžã¥ãŒã©ãŒã§ããããã¥ãŒããã¯ã¹ã§ã¯ãªãããã§ãã
解決ã¯1è¡ã§ããã
mv "$TARGET" "$CLAIMED_DIR/$BASENAME" 2>/dev/null || exit 0
POSIX ã® rename(2) ã¯åäžãã¡ã€ã«ã·ã¹ãã äžã§ã«ãŒãã«ãçŽååããŸããã確èªããŠããååŸãããPID ãã§ãã¯ãšã¯æ ¹æ¬çã«ç°ãªãã確èªãšååŸã1ã·ã¹ãã ã³ãŒã«ã®äžã§å®çµããŸããfd ç¶æ¿åé¡ãæ±ãã flock ãšãéããããã»ã¹ã®çæ»ãšããã¯ãåãé¢ãããŠããŸãããã¡ã€ã«èªäœãã¯ã¬ãŒã ã®ããŒã¯ã³ã«ãªããããã¯ã©ãã·ã¥ããŠãæ®çœ®ãã¡ã€ã«ã人éã«ãšã£ãŠèªã¿ãããç¶æ
ã§æ®ããŸãã
autolike-plist-reconcile.sh ã®ãèµ°è¡äžãªãäœãããªãããšãã PID ãã§ãã¯ãã¿ãŒã³ãšãä»åã® mv ã¯ã¬ãŒã ãã¿ãŒã³ã¯åãææ³ããçãŸããŠããŸããå·®åã¯ãTOCTOU ãæã€ãã©ãããã ãã§ããã©ã¡ãããä»è
ãåŠçäžãªãéåºããããšããéžæãæåã®æäœã§è¡ãèšèšã§ãã
èªåŸç°å¢ãé·æéå®å®ãããã«ã¯ããã®çš®ã®ãç«¶åãããããèš±ããªãæ§é ããç©ã¿éããŠãããããããŸãããäœæ¥å¯Ÿçã¯äºè±¡ã®å·ãå°ããããç°å¢å¯Ÿçã¯äºè±¡ãæ¶ããæå0åãã6ã¶æã§120äžåã®èŠæš¡ã«æã£ãŠãããã®ã¯ãå£ããæ¥ã«ã次ã¯ããããããã§ã¯ãªããæ§é ãçŽããéžæãç¹°ãè¿ããããã§ãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
- AIå°å ¥ã»èªååã®çžè«ãšå®è£ ãã³ãã¬7æ¬ã®é åžã¯ å ¬åŒLINE ããð¬
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ