ð æ»ãã PIDã2æéããã¯ãå æ ãã話
æ10äžã®å€§åŠçã ã£ãããããæ¯æ¥SNSãæŽæ°ãç¶ããã°çšŒããããšä¿¡ããŠã€ã³ã¹ã¿ãæã§æŽæ°ããŠããŸãããæ¯æ¥1æçš¿ãç¶ç¶30æ¥ãç²åŒããŠèŸããŸãããä»ã¯ Claude Code ã®èªåŸç°å¢ããã®äœæ¥ã代ããã«ãã£ãŠãããŠããã®ã§ãç§ã¯äžåæãåãããæå120äžãç¶æã§ããŠããŸãã
ãªããã®ä»çµã¿ãå¹ãã®ã
ãäœæ¥ãããããããç°å¢ãäœãããž
æã§SNSãæŽæ°ãç¶ããããšã®åé¡ã¯ãæå¿åãæ¶èããç¹ã«ãããŸããã©ãã ãç±éãé«ããŠããç¡ç ã»äœèª¿ã»ã¢ãããŒã·ã§ã³ã®ã ã©ã¯é¿ããããŸãããç¹ã«äŒç€Ÿéœåã§è§£éãããŠåå ¥ããŒãã«ãªã£ãææã¯ã粟ç¥çãªäœè£ããªã SNS æŽæ°ã©ããã§ã¯ãããŸããã§ããã
å幎ãã㊠Claude Code ã§æ§ç¯ããèªåŸç°å¢ã¯ãç§ã®ææ
ç¶æ
ã«é¢ä¿ãªãåããŸããlaunchd ãã¹ã¯ãªãããåŒã³ãã¹ã¯ãªããã claude -pïŒMAXæ ãææAPIçŠæ¢ïŒã§ã³ã³ãã³ããçæããçæç©ãèªåæçš¿ãã¥ãŒã«ç©ãŸããæ¯æ¥19:30ã«InstagramãžåºãŠããããã®ä»çµã¿ãæ£åžžã«åããŠããéããç§ãäœãããªããŠãæ120äžã®å£²äžãç¶æãããæ§é ã§ãã
èªè ã«æž¡ãããã¡ã³ã¿ã«ã¢ãã«
ãèªååïŒã¹ã¯ãªãããæžãããšæã£ãŠãã人ãå€ãã§ãããããã¯ååããæ£ãããªããã¹ã¯ãªããã¯æžããç¬éã ãæ£ãããæéãçµã€ãšå€éšã®äŸåå ãå£ããããã»ã¹ãäºæãã¬çç±ã§æ»ã«ãããã¯ãã¡ã€ã«ãæ®éªžã«ãªã£ãŠæ¬¡å以éã®å®è¡ããã¹ãŠæ¢ããã
æ¬åœã«å¹ãèªåŸç°å¢ã¯ãå£ããåæãšããŠãå£ããèªå·±ä¿®åŸ©ããã¬ã€ã€ãŒãæã€ããã®ã§ãä»åæ±ãããã¯ã®è©±ã¯ãã®å žåã§ãã
~/dev/brand-404/sns/gen_feature.py 㯠launchd ããæ¯æ¥å®æèµ·åããã Instagram ç¹éèšäºã®èªåçæã¹ã¯ãªããã§ãã1åã®å®è¡æéãé·ãïŒclaude -p ãæå€§3ååŒã³ãç»åçæãŸã§å«ãããšæ°ååïŒãããåã®å®è¡ãçµãã£ãŠããªãã®ã«æ¬¡ã®å®è¡ãéãªãããšãé²ãããã¯æ©æ§ãæã£ãŠããŸãã
ãã®ããã¯ãã¡ã€ã«ïŒ~/dev/brand-404/sns/gen_work/.lockïŒã®åãæ±ãã誀ã£ãããšã§ãpid 94799 ã kill æžã¿ã«ããããããããã¯ãè§£æŸããªããŸãŸæ®ãã以éã®å
šå®è¡ããlock held â çµäºãã§ã¹ããããããçæãçŽ2æéæ¢ãŸããŸããã
ãªã "2æé" ãªã®ã
ããã¯å€å®ã«ã¯ LOCK_STALE_SEC = 2 * 3600ïŒ2æéïŒãšãã宿°ããããŸãïŒgen_feature.py ã®57è¡ç®ïŒããããã¯ãååšããŠãmtimeã2æéããå€ããã°å¥ªåããããšããå®å
šåŒãšããŠèšãããã®ã§ãã
ãããåœåã®å®è£
ã§ã¯ãpid ã®çæ»ã確èªããã« mtime ã ããèŠãŠããŸãããkill æžã¿ã® pid ã .lock ã«æ®ã£ãŠããŠãmtimeã2æé以å
ã§ããã°ãå®è¡äžããšå€æããŠã¹ããããç¶ããŸããlaunchd ã3åã4åãšèµ·åããŠãã2æéãçµéãããŸã§å
šéšãlock held â çµäºãã
èªååããåããŠãããã®ã«ãäœãçæãããªãããšããææªã®ç¶æ ããµã€ã¬ã³ãã«2æéç¶ããŠããŸããã
ãç°å¢ãäœããåŽã«å¿ èŠãªèŠç¹
æåäœæ¥ãªããããã仿¥ã¯çæãããŠããªããªããšæ°ã¥ããŠæã§åããã°ããããããèªåŸç°å¢ãäœããšããããšã¯ãã誰ãèŠãŠããªãç¶æ ã§ãæ£ããåãç¶ãããããšãžã®è²¬ä»»ãåŒãåããããšã§ãã
pid ã®çæ»ãã§ãã¯ã¯äžèŠã念ã®ãããã«æããŸãããå®éã«ã¯ãã¹ã¯ãªããã kill ãããããšã«ããã¯ãæ®ããã±ãŒã¹ãå®åžžçã«çºçããŸããSIGKILL ã§å³æ»ãããå Žåãfinallyãããã¯ã® release_lock() ã¯äžåèµ°ããŸãããéçºäžã«æã§æ¢ããå Žåãåæ§ã§ããé·æéå®è¡ã®ã¹ã¯ãªããã launchd ã§å®æèµ·åãããªããpid çæ»ãã§ãã¯ã¯å¿
é ã§ãã
å šäœã®æµã
ã·ã¹ãã å šäœã®ã¢ãŒããã¯ãã£
launchd (æ¯æ¥å®æ + æ¯æ¥19:30)
â
ââ gen_feature.pyïŒæ¯æ¥å®æïŒ
â acquire_lock() â ä»åã®è©±
â â
â ãã¥ãŒæ®æ°ãã§ãã¯
â QUEUE_TARGET=3 ã«äžè¶³ãããã°
â â
â brand-catalog.json ãããã©ã³ãéžå®
â â
â Shopify /products.json ååŸ
â â
â claude -p #1: ã³ããŒçæ (copy.md)
â â
â claude -p #2: ç»å圹å²éžå® (images.md)
â â
â ã¹ã©ã€ãçæ (build-post-from-json.mjs)
â â
â claude -p #3: ã»ã«ãQA (qa.md)
â â QAãéé
â content/sns/feature-XX-{slug}/ ã«åºå
â release_lock()
â
ââ ig_autopost.pyïŒæ¯æ¥19:30ïŒ
content/sns/feature-* ã®ãã¥ãŒãã1æ¬åãåºã
â
Instagram Graph API ã§æçš¿
â
state/ig_posted.jsonl ã«èšé²
ãã¥ãŒåšåº«ã¯åžžæ3æ¬ãç®æšã«ããŠããŸãïŒgen_feature.py 60è¡ç® QUEUE_TARGET = 3ã61è¡ç® MAX_GEN_PER_RUN = 2ïŒãåšåº«ã3æ¬ããã°ãçæã1ã2æ¥é£ç¶ã§å€±æããŠãæçš¿ã¯éåããªãèšèšã§ãã
ããã¯æ©æ§ã®è²¬å
1å®è¡ãé·ãïŒæå€§ CLAUDE_TIMEOUT = 600ç§ Ã 3åïŒç»åDLïŒã¹ã¯ãªããã launchd ã®å®æèµ·åã§ç®¡çããå Žåãæ¬¡ã®åé¡ãèµ·ããŸãã
- ååã®å®è¡ãçµãã£ãŠããªãã®ã«æ¬¡ã®ã€ã³ã¹ã¿ã³ã¹ãèµ·åããåããã©ã³ããäºéçæãã
- å
±æã®
brand-catalog.jsonãåæã«æžãæããŠç Žæãã
ãããé²ãã®ã gen_work/.lock ã§ããããã¯ãã¡ã€ã«ã«èªããã»ã¹ã® PID ãæžã蟌ã¿ã次åèµ·åæã«ãã® PID ã®çæ»ã確èªããŠããåäœãããã©ãããæ±ºããŸãã
acquire_lock() ã®å®è£ ïŒä¿®æ£åŸïŒ
gen_feature.py ã®225ã249è¡ç®ãå®è£
ã®å
šäœã§ãã
LOCK_STALE_SEC = 2 * 3600 # 57è¡ç®
def acquire_lock() -> bool:
GEN_WORK.mkdir(parents=True, exist_ok=True)
if LOCK_FILE.exists():
age = time.time() - LOCK_FILE.stat().st_mtime
try:
pid = int(LOCK_FILE.read_text(encoding="utf-8").strip())
if pid <= 0:
raise ValueError
except (OSError, ValueError):
log("stale lock 奪å: PIDã空ãŸãã¯éæ°å€")
else:
try:
os.kill(pid, 0) # â ããã»ã¹çæ»ãã§ãã¯
except ProcessLookupError:
log(f"stale lock 奪å: pid={pid} ã¯äžåš")
except PermissionError:
if age <= LOCK_STALE_SEC:
return False
log("stale lock 奪å: mtime 2hè¶
")
else:
if age <= LOCK_STALE_SEC:
return False
log("stale lock 奪å: mtime 2hè¶
")
LOCK_FILE.write_text(str(os.getpid()), encoding="utf-8")
return True
ä¿®æ£åïŒåé¡ããã£ãã³ãŒãïŒã®å€å®ããžãã¯ã¯ãmtime ã ããèŠãŠããŸããã
# â ä¿®æ£å: pid ã®çæ»ãèŠãªã
def acquire_lock() -> bool:
GEN_WORK.mkdir(parents=True, exist_ok=True)
if LOCK_FILE.exists():
age = time.time() - LOCK_FILE.stat().st_mtime
if age <= LOCK_STALE_SEC:
return False # â killæžã¿pidã§ã2h以å
ã¯å
šéšããã§è¿ã
log("stale lock 奪å: mtime 2hè¶
")
LOCK_FILE.write_text(str(os.getpid()), encoding="utf-8")
return True
os.kill(pid, 0) ã®æå³
os.kill(pid, 0) ã¯ã·ã°ãã« 0ïŒãã«ã·ã°ãã«ïŒãéããŸããã·ã°ãã« 0 ã¯å®éã«ã¯äœãéä¿¡ããŸãããã«ãŒãã«ã pid ã確èªããããã»ã¹ãååšããŠéä¿¡æš©éãããã°æ£åžžçµäºãããã»ã¹ãäžåšãªã ProcessLookupErrorãããã»ã¹ã¯ååšãããå¥ãŠãŒã¶ãŒã®ãã®ãªã PermissionError ãè¿ãã ãã§ãã
ãã®æåã䜿ããšãããã»ã¹ã®çæ»ãå®å šã«ç¢ºèªã§ããŸãã
| os.kill(pid, 0) ã®çµæ | æå³ | acquire_lock ã®å€æ |
|---|---|---|
| äŸå€ãªã | pid ã®ããã»ã¹ãååšã»å®è¡äž | mtime ã 2h 以å ãªããå®è¡äžããšããŠã¹ããã |
| ProcessLookupError | pid ã®ããã»ã¹ãäžåšïŒkill æžã¿çïŒ | æ®éªžããã¯ãšããŠå³åº§ã«å¥ªå |
| PermissionError | pid ã¯ååšãããå¥ãŠãŒã¶ãŒææ | mtime fallbackïŒ2h è¶ ãªã奪åïŒ |
pid 94799 ã kill ãããŠããä»åã®ã±ãŒã¹ã§ã¯ãä¿®æ£åŸã®ã³ãŒããªã ProcessLookupError ãçºçããŠå³åº§ã« "stale lock 奪å: pid=94799 ã¯äžåš" ãšãã°ãåºããæ¬¡ã®å®è¡è
ãããã¯ãåããŸãã
ã·ã§ã«çã® kill -0 ã€ãã£ãªã
åããã¿ãŒã³ã¯ ~/.claude/scripts/automation-health.sh ã®å€éèµ·åã¬ãŒãïŒ20ã30è¡ç®ïŒã§ã䜿ãããŠããŸãã
_ah_lock="${TMPDIR:-/tmp}/automation-health.lock"
if ! mkdir "$_ah_lock" 2>/dev/null; then
if kill -0 "$(cat "$_ah_lock/pid" 2>/dev/null)" 2>/dev/null; then
echo "automation-health: å¥ã€ã³ã¹ã¿ã³ã¹çšŒåäžã®ããã¹ããã" >&2
exit 0
fi
rm -rf "$_ah_lock"
mkdir "$_ah_lock" 2>/dev/null || { echo "lockååŸå€±æã»ã¹ããã" >&2; exit 0; }
fi
echo $$ > "$_ah_lock/pid"
trap 'rm -rf "$_ah_lock"' EXIT
ã³ã¡ã³ãïŒ17ã20è¡ç®ïŒã«çç±ãæžããŠãããŸããã--deep ã¯å
šèµ°æ»ã§ I/O ãç©ã¿äžããå€éèµ·åãããš load average ãè·³ãããã¢ãããã¯ãª mkdir ããã¯ã§1æ¬ã«çµããPIDçåãã§ãã¯ã§æ®éªžããã¯ã¯èªå奪åãtrap EXIT ã§æ£åžž/ç°åžžçµäºã©ã¡ãã§ãå¿
ãè§£æŸãã
shell ã® kill -0 <pid> 㯠Python ã® os.kill(pid, 0) ãšå
šãåãæå³è«ã§ãã2>/dev/null ã§ stderr ãæšãŠãŠããã®ã¯ããããã»ã¹äžåšãã®ãšã©ãŒã¡ãã»ãŒãžããŠãŒã¶ãŒã«èŠããªãããã§ããããã»ã¹ãååšããã°çµäºã³ãŒã 0ãäžåšãªãçµäºã³ãŒã 1 ãè¿ãã®ã§ãif kill -0 ... ã®æåŠãçæ»å€å®ã«ãªããŸãã
ããã« trap 'rm -rf "$_ah_lock"' EXIT ã§ãæ£åžžçµäºã»ãšã©ãŒçµäºã»ã·ã°ãã«åä¿¡ã©ãã§ãããã¯è§£æŸãä¿èšŒããŠããŸããPython ã® try/finally ã§ release_lock() ãåŒã¶ã®ãšåãçºæ³ã§ãã
2ã€ã®å®è£ ã䞊ã¹ããšèŠããããš
Python (gen_feature.py) Shell (automation-health.sh)
âââââââââââââââââââââ âââââââââââââââââââââââââââââ
os.kill(pid, 0) kill -0 <pid>
ProcessLookupError â 奪å çµäºã³ãŒã1 â æ®éªž â 奪å
PermissionError â mtime fb çµäºã³ãŒãâ 0â åäž
äŸå€ãªã â å®è¡äž çµäºã³ãŒã0 â å®è¡äž
try/finally release_lock() trap 'rm -rf lock' EXIT
èšèªãéã£ãŠããããã¯ååŸã®æ£ãããã¿ãŒã³ã¯åã3ã¹ãããã§ãã
- ããã¯ãã¡ã€ã«ããPIDãèªã
kill -0/os.kill(pid, 0)ã§ããã»ã¹ã®çæ»ã確èªãã- äžåšãªãå³å¥ªåãååšãããªã mtime ã坿¬¡çãªå®å šåŒãšããŠäœ¿ã
mtime ã¯ãããŸã§ãpid ãã§ãã¯ãã§ããªãã£ãæã®æçµææ®µãã§ãããäž»å€å®ã«ããŠã¯ãããŸããã
å®è£ ã®è©³çް
release_lock() ãš try/finally ã®çµã¿åãã
acquire_lock() ã®è©±ãããããããã¯ã®æ£ãããã¿ãŒã³ã¯ãªãªãŒã¹åŽã«ãèããããŸãã
gen_feature.py 252ã256è¡ç®ã® release_lock() ã¯ã·ã³ãã«ã§ãã
def release_lock() -> None:
try:
LOCK_FILE.unlink()
except FileNotFoundError:
pass
FileNotFoundError ãæ¡ãã€ã¶ããŠããã®ã¯æå³çã§ããfinally ãããã¯ãšæååŒã³åºããéãªã£ãå Žåã«ã¯ã©ãã·ã¥ããªãããã®é²åŸ¡ã§ããããããã¯è§£æŸã«å€±æããŠæ¬åŠçãäŸå€ã«ãªãããšããæ¬æ«è»¢åãé²ããŸãã
éèŠãªã®ã¯åŒã³åºãåŽã® main() ã®æ§é ã§ãïŒ995ã1020è¡ç®ïŒã
if not acquire_lock():
log("lock heldïŒä»ããã»ã¹ãå®è¡äž or 2h以å
ïŒâ çµäº")
return 0
try:
made = 0
attempts = 0
max_attempts = need + 3
attempted_brands: set[str] = set()
while made < need and attempts < max_attempts:
result = run_pipeline(attempted_brands)
...
log(f"çæå®äº: {made}/{need}æ¬ è©Šè¡{attempts}å (queueæ® {count_queue_remaining()}æ¬)")
finally:
release_lock()
try/finally ã§ããã¯ãååŸããåŸã®å
šåŠçãå
ãã§ããŸããrun_pipeline() ã®éäžã§äŸå€ãçºçããŠã finally ãããã¯ã¯å¿
ãå®è¡ããããããããã¯ã¯ç¢ºå®ã«è§£æŸãããŸãã
ãã ãããããæ©èœããªãäŸå€ããããŸããSIGKILL ã§ããkill -9 <pid> ã§ããã»ã¹ã匷å¶çµäºããå ŽåãPython ã®ã©ã³ã¿ã€ã ããšå³æ»ãããã finally ãããã¯ã¯èµ°ããŸããããããä»åã®äºæ
ã®çŽæ¥åå ã§ããã
trap EXIT ãåæ§ã§ãïŒautomation-health.sh 30è¡ç®ïŒã
trap 'rm -rf "$_ah_lock"' EXIT
EXIT ãã©ãã㯠SIGTERMïŒéåžžã® killïŒãæ£åžžçµäºããšã©ãŒçµäºã§èµ°ããŸãããSIGKILL ã§ã¯èµ°ããŸãããã©ã¡ãã®èšèªã§ãã匷å¶çµäºã«ããããã¯æ®éªžã¯ try/finally ãš trap EXIT ã ãã§ã¯é²ããªããã ãã os.kill(pid, 0) ã«ããæ»æŽ»ç¢ºèªãå¿
èŠã«ãªãã®ã§ãã
3ã€ã®äŸå€åå²ãããããã®çç±
acquire_lock() ã® os.kill(pid, 0) åŒã³åºãã«ã¯3ã€ã®åå²ããããŸãïŒ235ã247è¡ç®ïŒã
try:
os.kill(pid, 0)
except ProcessLookupError:
log(f"stale lock 奪å: pid={pid} ã¯äžåš")
except PermissionError:
if age <= LOCK_STALE_SEC:
return False
log("stale lock 奪å: mtime 2hè¶
")
else:
if age <= LOCK_STALE_SEC:
return False
log("stale lock 奪å: mtime 2hè¶
")
ProcessLookupError ã¯ããã»ã¹ãååšããªãã±ãŒã¹ã§ããä»åã®äºæ
ïŒkillæžã¿pidïŒãããã«ããããŸããå³åº§ã«å¥ªåããŸããmtime ãèŠãå¿
èŠã¯ãããŸããã
PermissionError 㯠pid ãååšããŠãããå¥ãŠãŒã¶ãŒã®ããã»ã¹ãšããã±ãŒã¹ã§ããã·ã°ãã«éä¿¡æš©éããªãããååšã¯ç¢ºèªã§ããŸãããããããèªåã®ã¹ã¯ãªããã®åã®ã€ã³ã¹ã¿ã³ã¹ããã¯ç¢ºèªã§ããŸããããã®ç¶æ³ã§ã¯ mtime fallback ã«é Œããããªãã®ã§ã2æé以å
ãªããå®è¡äžãšããŠæ±ããæ¹åã«åããŠããŸãã
äŸå€ãªãïŒelse ãããã¯ïŒ ã¯ããã»ã¹ãååšããŠéä¿¡æš©éããããã€ãŸã確å®ã«ããŸã åããŠãããç¶æ ã§ãããã¡ãã mtime ã2æé以å ãªãæ£åœãªå®è¡äžãšããŠæ±ããŸãã
LOCK_STALE_SEC = 2 * 3600 ã¯ãããŸã§æçµææ®µã®å®å
šåŒã§ããpid ãã§ãã¯ãéçšããªã PermissionError ã±ãŒã¹ãšãelse ãããã¯ã§ããã»ã¹ãåããŠããã®ã«äœããã®çç±ã§æ³å®å€ã«é·æéããã£ãŠããã±ãŒã¹ã«ã ãæ©èœããŸããååã§ç€ºãããmtime ãäž»å€å®ã«ããŠã¯ãããªãããšãã話ã¯ããã«ã€ãªãããŸãã2æéãšãããããå€ãæ£ãããã©ããã¯åé¡ã®æ¬è³ªã§ã¯ãªããpid ãã§ãã¯ãå
ã«ããããšãæ¬è³ªã§ãã
PID ãäžæ£å€ã ã£ãå Žåã®åŠç
ããã¯ãã¡ã€ã«ãååšãããäžèº«ãå£ããŠããã±ãŒã¹ãåŠçããŠããŸãïŒ229ã234è¡ç®ïŒã
try:
pid = int(LOCK_FILE.read_text(encoding="utf-8").strip())
if pid <= 0:
raise ValueError
except (OSError, ValueError):
log("stale lock 奪å: PIDã空ãŸãã¯éæ°å€")
OSError ã¯ãã¡ã€ã«ãèªããªãã£ãå ŽåïŒããŒããã·ã§ã³åé¡çïŒãValueError 㯠int 倿ã«å€±æããå ŽåãŸãã¯pidã0以äžã®å Žåã§ããã©ã¡ãããå€å®äžèœïŒæ®éªžããšããŠå¥ªåããŸãã
pid <= 0 ããã§ãã¯ããŠããã®ã¯ãPIDã¯å¿
ãæ£ã®æŽæ°ãšãã POSIX ã®ä¿èšŒã䜿ã£ãããªããŒã·ã§ã³ã§ããint("0") ã int("-1") ã¯å€æã«æåããŸãããå®ããã»ã¹ã® PID ãšããŠæå¹ãªå€ã§ã¯ãããŸããã空ãã¡ã€ã«ã strip() ããçµæã空æååã®å Žå㯠int("") ã§ ValueError ã«ãªãã®ã§åãçµè·¯ã«èœã¡ãŸãã
ã·ã§ã«ã® mkdir ããã¯ãæã€ååæ§
automation-health.sh ã mkdir ã§ããã¯ãå®è£
ããŠããã®ã¯ïŒ22è¡ç®ïŒããã¡ã€ã«ãžã®æžã蟌ã¿ãããååçã ããã§ãã
if ! mkdir "$_ah_lock" 2>/dev/null; then
if kill -0 "$(cat "$_ah_lock/pid" 2>/dev/null)" 2>/dev/null; then
echo "automation-health: å¥ã€ã³ã¹ã¿ã³ã¹çšŒåäžã®ããã¹ããã" >&2
exit 0
fi
rm -rf "$_ah_lock"
mkdir "$_ah_lock" 2>/dev/null || { echo "lockååŸå€±æã»ã¹ããã" >&2; exit 0; }
fi
echo $$ > "$_ah_lock/pid"
mkdir ã¯ã«ãŒãã«ã¬ãã«ã§ã¢ãããã¯ã§ãããã£ã¬ã¯ããªãååšããªããã°äœæããŠæåãååšããã°å€±æããã2ã€ã®ããã»ã¹ãåæã« mkdir ãåŒãã§ããã«ãŒãã«ãäžæ¹ããæåãããŸããã
Python ã® write_text() ã«ã¯ãã®ä¿èšŒããããŸãããA ãå
ã«æžããB ãçŽåŸã«äžæžãããç«¶åãçè«äžããããŸããgen_feature.py ã®å Žåãlaunchd ãã€ã³ã¿ãŒãã«èµ·åãªã®ã§åæå®è¡ã®ç¢ºçã¯æ¥µããŠäœãã§ããããŒãã§ã¯ãããŸãããPython ã§åãååæ§ãåŸããªã os.mkdir() ãäœ¿ãæ¹æ³ããããŸããgen_feature.py ããããŠãã®æ¹åŒãæ¡çšããŠããªãã®ã¯ããã®ã¹ã¯ãªããã§çºçé »åºŠã®é«ãåé¡ããåæèµ·åç«¶åãã§ã¯ãªããkillæžã¿pidãæ®ããã ã£ãããã§ãã解決ãã¹ãåé¡ã®åªå
é äœã«åãããŠã·ã³ãã«ãªå®è£
ãéžãã§ããŸãã
ç§ãè©°ãŸã£ã話
çç¶ïŒäœãçæãããªãã®ã«ããšã©ãŒãããªã
æåã®ç°å€ã«æ°ã¥ããã®ã¯ Discord ã® #brand-404 ãã£ã³ãã«ã§ããã忥ãŸã§æ¯æ¥å±ããŠãããð o81 IGç¹éçæ: ...ããšããéç¥ãæ¥ãªãã
Discord ã®ã¢ã©ãŒããã£ã³ãã«ã«ãäœãæ¥ãŠããŸãããããšã©ãŒãåºããã§ã¯ãªããäœãèµ·ããªãã£ãããšããçç¶ã§ããã
launchd ã®ãžã§ããèœã¡ãŠããã®ããšæã automation-health.sh ãèµ°ããããšãcom.lily.gen-feature ã¯ãããŒãæž / last exit=0ãã§ãããexit 0 ã¯æ£åžžçµäºã§ããã§ãçæã¯æ¥ãŠããªãã
ãã°ãæç¹°ããšããããªè¡ã3æéå䞊ãã§ããŸããã
[gen] lock heldïŒä»ããã»ã¹ãå®è¡äž or 2h以å
ïŒâ çµäº
3ååã® launchd èµ·åãå šéšããã§çµãã£ãŠããŸãããããã¯ã誰ããå æããŠããã
åå ç¹å®ïŒããã¯ãã¡ã€ã«ãçŽæ¥èŠã
~/dev/brand-404/sns/gen_work/.lock ã cat ãããšã
94799
pid 94799 ãæžããŠãããŸãããps aux | grep 94799 ã§äœãåºãŸãããçããŠããªãããã»ã¹ã§ãã
ls -la ~/dev/brand-404/sns/gen_work/.lock ã§ mtime ã確èªãããšãçŽ1.5æéåã®ã¿ã€ã ã¹ã¿ã³ãã§ãããLOCK_STALE_SEC = 2 * 3600ïŒ2æéïŒã«å¯ŸããŠãŸã 0.5æéè¶³ããªããã ãããã£ãšã2h以å
= å®è¡äžããšå€æããŠã¹ããããç¶ããŠããã®ã§ãã
pid 94799 ãæ»ãã ã®ã¯ãåå€ã®ãããã°äžã«å¥ã¿ãŒããã«ã§ kill -9 $(cat ~/dev/brand-404/sns/gen_work/.lock) ãå®è¡ããããã§ãããæ¬æ¥ãªã release_lock() ãèµ°ã£ãŠ .lock ãæ¶ããã¯ãã§ããããã -9ïŒSIGKILLïŒã¯ finally ãããã¯ãé£ã°ããŸããããã¯ãæ®ã£ããŸãŸç¿æã® launchd èµ·åãå
šéšå·»ã蟌ãã ã®ã§ãã
æåã®èª€ã£ãä¿®æ£ïŒLOCK_STALE_SEC ãçããã
æåã«æãã€ããã®ã¯ã2æéã¯é·ãããã30åã«ããããã§ããã
å®éã« LOCK_STALE_SEC = 30 * 60 ã«å€ããŠã³ãããããŸãããããã¯ééãã§ããã
åé¡ã¯ã2æéãé·ãããšãã§ã¯ãªããkillæžã¿pidãçããŠãããšå€æããããšãã§ãã30åã«ããŠããkillåŸ30åã¯åãåé¡ãç¶ç¶ããŸãããããä»åºŠã¯ãæ£åœãªé·æéå®è¡ïŒclaude -p ã3ååŒã¶ = æå€§ CLAUDE_TIMEOUT = 600 ç§ Ã 3åïŒç»åDLïŒãéäžã§å¥ªåããŠããŸããªã¹ã¯ãçãŸããŸãã
æ£ããä¿®æ£ã¯ãmtime ãèŠãåã« pid ã®çæ»ãå
ã«ç¢ºèªãããã§ãã£ãŠãmtime ã®ãããå€ãå€ããããšã§ã¯ãããŸããããããå€ã®èª¿æŽã¯æ ¹æ²»ã§ã¯ãªããçç¶ã軜ãããã ãã®å¯ŸåŠã§ãããã®ã³ãããã¯åãæ¶ããos.kill(pid, 0) ãå
ã«ç¢ºèªããçŸåšã®æ¹éã«åãæ¿ããŸããã
äºåºŠç®ã®è©°ãŸãïŒPermissionError ãèŠèœãšãã
ProcessLookupError ã ããæãŸããæåã®ä¿®æ£çãæ¬çªã«å
¥ããåŸããã¹ãäžã«åã³è©°ãŸããŸããã
確èªç®çã§ sudo python3 ~/dev/brand-404/sns/gen_feature.py --dry-run ãäžåºŠèµ°ãããåŸãéåžžãŠãŒã¶ãŒãšããŠèµ°ããããšåã³ããã¯ãæ®ãçç¶ãåºãŸããã
sudo ã§èµ°ã£ãããã»ã¹ã¯ root ææã§ããéåžžãŠãŒã¶ãŒãã os.kill(root_pid, 0) ãåŒã¶ãš PermissionError ãè¿ããŸããç§ã®æåã®ä¿®æ£ã¯ ProcessLookupError ããèŠãŠããªãã£ããããPermissionError ãå€ãžäŒæããŠã¹ã¿ãã¯ãã¬ãŒã¹ãåºãŠããŸããã
# â PermissionError ãèŠèœãšããäžéããŒãžã§ã³
try:
os.kill(pid, 0)
except ProcessLookupError:
log(f"stale lock 奪å: pid={pid} ã¯äžåš")
# PermissionError ã¯æãŸããŠããããå€ãžäŒæãã
else:
if age <= LOCK_STALE_SEC:
return False
Python ã® try/except/else ã®æ§é ãšããŠãelse ã¯ãtry ãããã¯ã§äŸå€ãäžååºãªãã£ãå Žåãã«å®è¡ãããŸããPermissionError ãæãŸããŠããªãã®ã§ããã®ã±ãŒã¹ã§ã¯ except ProcessLookupError ã«ã else ã«ãå
¥ãããäŸå€ããã®ãŸãŸåŒã³åºãå
ã«äŒãã£ãŠããŸãããæçµçã« PermissionError ãæç€ºçã«æãŸããmtime fallback ã«åãçŸåšã®åœ¢ïŒ241ã243è¡ç®ïŒã«ãªããŸããã
except PermissionError:
if age <= LOCK_STALE_SEC:
return False
log("stale lock 奪å: mtime 2hè¶
")
æèšïŒãæ£åžžçµäºããŠãããã¯ãæ£ããåããŠãããã§ã¯ãªã
ãã®ä»¶ã§æã匷ãå»ãŸããã®ã¯ãexit 0 ãšãçæãå®äºãããã¯å¥ç©ã ãšããããšã§ãã
log("lock held â çµäº"); return 0 㯠exit 0 ã§ããlaunchd ã®ãžã§ãå±¥æŽã¯ãæåããšããŠèšé²ãããŸããautomation-health.sh ãã°ãªãŒã³ãè¿ããŸããDiscord ã®ã¢ã©ãŒããæ¥ãŸãããå€ããèŠããšå
šéšæ£åžžã§ãã§ãç®çã®ãã³ã³ãã³ãçæãã¯äžåèµ·ããŠããªãã
ãã®çš®ã®ãµã€ã¬ã³ãé害ã¯ãã¢ãã¿ãªã³ã°ããããã»ã¹ãæ»ãã ãã©ãããã§ã¯ãªããæå³ããå¯äœçšãçºçãããã©ãããã§èšèšããªããšæ€ç¥ã§ããŸããã
ä»ã¯ãã¥ãŒæ®æ°ïŒcount_queue_remaining()ïŒãäžå®æéãŒãã®ãŸãŸãªã Discord ã«ã¢ã©ãŒããé£ã°ãç£èŠãå¥ã§æã£ãŠããŸããããã»ã¹ã®å¥å
šæ§ãã§ãã¯ãšãåºåã®å¥å
šæ§ãã§ãã¯ã¯åããŠèšèšããå¿
èŠããããŸããèªåŸç°å¢ããå£ããåæãšããèšèšãã«ãããšããããšã¯ãããã»ã¹ãçããŠãããã©ããã ãã§ãªããããã®ç°å¢ãæå³ããåºåãçã¿ç¶ããŠãããããå¥ã®ã¬ã€ã€ãŒã§ç£èŠããããšãå«ã¿ãŸãã
ã€ãŸãããã€ã³ã
ååãšäžæ®µã§ã¯ãmtime ã ããèŠãŠããèšèšã®æ¬ é¥ããos.kill(pid, 0) ã«ããä¿®æ£ããPermissionError ã®èŠèœãšãããæ±ããŸãããããã§ã¯ãåãæ§é ã®ã¹ã¯ãªãããåããäžã§å®éã«è©°ãŸã£ãç¹ãè£è¶³ããŸãã
PID ãåå©çšããããšãçããŠããã誀å€å®ãèµ·ãã
Linux/macOS ã® PID ã¯æéã§æãè¿ããŸããmacOS ã§ã¯æå€§ 99999 ä»è¿ã§äžåšããŸããé·æé皌åããç°å¢ã§ã¯ãæšæ¥ kill -9 ããããã»ã¹ã® PID ã仿¥ã®ç¡é¢ä¿ãªå¥ããã»ã¹ã䜿ã£ãŠããããšããããšãèµ·ããåŸãŸãããã®ç¶æ
ã§ os.kill(pid, 0) ãåŒã¶ãšãããã»ã¹ãçããŠããããšå€å®ãããŸããgen_feature.py ã¯çŸç¶ PID ã®ã¿ã .lock ã«æžããŠããŸããïŒ248è¡ç® LOCK_FILE.write_text(str(os.getpid()), ...)ïŒãlaunchd ã®ã€ã³ã¿ãŒãã«ã1æ¥1åã§ããããšãš LOCK_STALE_SEC = 2 * 3600ïŒ57è¡ç®ïŒã®çµã¿åããã§å®å®³ã¯ã»ãŒãããŸãããé«é »åºŠã§åŒã°ããã¹ã¯ãªããã§ã¯ PID ãšéå§ã¿ã€ã ã¹ã¿ã³ãããã¢ã§æžããäž¡æ¹äžèŽããå Žåã®ã¿ãå®è¡äžããšå€æããå®è£
ãæ€èšããŸãã
GEN_WORK.mkdir(parents=True, exist_ok=True) ãå¿ãããšååå®è¡ã峿»ãã
acquire_lock() ã®226è¡ç®ã¯ GEN_WORK.mkdir(parents=True, exist_ok=True) ããå§ãŸããŸãããã®1è¡ããªãå Žåãgen_work/ ãã£ã¬ã¯ããªãååšããªãååèµ·åã§ LOCK_FILE.exists() ããåã®æäœã FileNotFoundError ãäžããŸããlaunchd ãèµ·åããŠãã¹ã¯ãªããã峿»ããlast exit=1 ã®ãŸãŸäœãåããªãç¶æ
ã«ãªããŸããè§£æŸããåã«äœæããããšããé çªãå®ããŸãã
write_text() ã¯ã¢ãããã¯ã§ã¯ãªãïŒçè«äžã®ç«¶åãååšããïŒ
Python ã® Path.write_text() ã¯æžã蟌ã¿éäžã®ç¶æ
ãä»ããã»ã¹ãèªããå¯èœæ§ããããŸããLOCK_FILE.write_text(str(os.getpid()), ...) ã®æžã蟌ã¿ãå®äºããåã«æ¬¡ã®ã€ã³ã¹ã¿ã³ã¹ã read_text() ãããšç©ºæååãèªã¿ãint("") ã ValueError â æ®éªžãšããŠå¥ªå â 2ã€ã®ã€ã³ã¹ã¿ã³ã¹ãåæå®è¡ãããããšããç«¶åãçè«äžæç«ããŸããgen_feature.py 㯠launchd ããã®éæ¬ èµ·åãªã®ã§åæå»èµ·åã¯ã»ãŒèµ·ããŸãããããã1åããšã«åŒã°ãããããªé«é »åºŠã¹ã¯ãªããã§ã¯ os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY) ã§ã¢ãããã¯ãªæžã蟌ã¿ã䜿ããŸããautomation-health.sh ã mkdir ã§ããã¯ãå®è£
ããŠããçç±ã¯ããã«ãããŸãïŒ22è¡ç® if ! mkdir "$_ah_lock" 2>/dev/null;ïŒã
finally ãèµ°ããªãã®ã¯ SIGKILL ã ãã§ã¯ãªã
SIGKILLïŒkill -9ïŒã try/finally ãé£ã°ãããšã¯äžæ®µã§æ±ããŸãããããäžã€æ³šæãå¿
èŠãªã®ã¯ãC æ¡åŒµã¢ãžã¥ãŒã«ãå
éšã§ os._exit() ãåŒã¶ã±ãŒã¹ã§ããsys.exit() 㯠SystemExit äŸå€ãšã㊠finally ãèµ°ããŸãããos._exit() ã¯ããã»ã¹ãã©ã³ã¿ã€ã ããšå³çµäºããããã finally ã¯èµ°ããŸãããå€éšã©ã€ãã©ãªãžã®äŸåãå¢ããã»ã©ãfinally ãèµ°ããªãå¯èœæ§ãã¯äžãããŸãããfinally ãèµ°ããªãåæã§ pid çæ»ãã§ãã¯ãèšèšãããããšãå¯äžã®æ ¹æ¬å¯Ÿçã§ãã
ããã¯ãã¡ã€ã«ã®ãã¹ãçžå¯Ÿãã¹ã§çµã¿ç«ãŠããšç°å¢äŸåã«ãªã
launchd ã® plist ã« WorkingDirectory ããŒãèšå®ãããŠãããšãã¹ã¯ãªããã®èµ·åãã£ã¬ã¯ããªãããã«å€ãããŸããçžå¯Ÿãã¹ã§ããã¯ãã¡ã€ã«ã®ãã¹ãçµã¿ç«ãŠããšãplist ã® WorkingDirectory 次第ã§å¥ã®å Žæã«ããã¯ãã¡ã€ã«ãäœãããŸããgen_feature.py 㯠ROOT = Path(__file__).resolve().parent.parentïŒ36è¡ç®ïŒããå
šãã¹ã絶察ãã¹ã§çµã¿ç«ãŠãŠããŸããlaunchd ãžã§ãã§åãã¹ã¯ãªãã㯠__file__ ããŒã¹ã®çµ¶å¯Ÿãã¹ã䜿ããŸãã
_draft-feature-XX-{slug} ãã£ã¬ã¯ããªãèç©ãã
run_pipeline() 㯠QA 倱æã»ã¹ã©ã€ãçæå€±æã®ãšã _draft-feature-XX-{slug} ãã£ã¬ã¯ããªãåé€ããã«æ®ããŸããããã¯ã倱æããã³ã³ãã³ããåŸãã確èªã§ããããã«ãããæå³çãªèšèšã§ãïŒ954ã959è¡ç®ïŒãcount_queue_remaining() 㯠_draft- prefix ã®ãã®ãã«ãŠã³ãããé€å€ããŠããããïŒ203ã218è¡ç®ïŒãåšåº«èšç®ã«ã¯åœ±é¿ããŸããããã ãèç©ãç¶ããš content/sns/ ããŽãã ããã«ãªããŸããææ¬¡ã® find content/sns -name '_draft-*' -mtime +30 -type d ã§ã®æž
æãå¿
èŠã§ãã
exit 0 ããæ£åžžããšè§£éãããšãµã€ã¬ã³ãé害ãéãæãã
main() 颿°ã¯ãããã¯ååŸå€±ææã return 0 ã§çµãããŸãïŒ996ã997è¡ç®ïŒãlaunchd ã¯ããããæ£åžžçµäºããšããŠèšé²ããautomation-health.sh ã® launchd ãã§ãã¯ã last exit=0 ã â ãšè¡šç€ºããŸããããã¥ãŒåšåº«ãè¶³ããŠããŠçæäžèŠã ã£ã exit 0ããšãããã¯ååŸã«å€±æããŠäœãããªãã£ã exit 0ãã launchd ã®ç®ã«ã¯åãã«èŠããŸããå®éã®2æé忢ã¯ããã§èŠããŸããã§ããã
ããšã©ãŒãæ¥ãªãïŒæ£åžžããšããèšèšææ³ã®çœ
Discord ãžã®ã¢ã©ãŒã㯠discord_alert() ãåŒã°ãã倱æã±ãŒã¹ã ãæ¥ãŸãïŒ140ã142è¡ç®ïŒãããã¯ååŸå€±æã¯ãšã©ãŒæ±ãã§ã¯ãªãã¹ããããªã®ã§ã¢ã©ãŒãã¯æ¥ãŸãããèªååç£èŠããæªãããšãèµ·ãããéç¥ãã ãã§èšèšãããšããµã€ã¬ã³ãã¹ãããã¯æ°žé ã«æ€ç¥ã§ããŸãããä»åã®ç°å€ã«æåã«æ°ã¥ããã®ãã#brand-404 ãã£ã³ãã«ã®ãð o81 IGç¹éçæ: ...ããšããæåéç¥ïŒdiscord_post_review() = 973è¡ç®ïŒãæ¥ãªãã£ãããšã§ããã
Discord ã®ããã©ã«ã User-Agent ã Cloudflare ã«åŒŸããã
_discord_post() ã®ã³ã¡ã³ãïŒ93ã94è¡ç®ïŒã«ããéããPython ã® urllib ãããã©ã«ãã§éã Python-urllib/x.y ãšãã User-Agent ã Cloudflare ã 403/1010 ã§åŒŸããŸããua_header = "lily-o81-gen/1.0" ãæç€ºããŠåé¿ããŠããŸããèªååã¹ã¯ãªããã§ urllib ã requests ã©ã€ãã©ãªã®ããã©ã«ãèšå®ã®ãŸãŸå€éšãµãŒãã¹ãå©ããšãäºåãªããããã¯ãããããšããããŸãã
curl ã®ã¿ã€ã ã¢ãŠããšãªãã©ã€ã®ãã©ã³ã¹ãèŠèª€ã
fetch_products() 㯠Shopify ã® products.json ã attempts=3 ã§ãªãã©ã€ããŸãïŒ503ã522è¡ç®ïŒã倱ææã«15ç§åŸ
ã£ãŠãªãã©ã€ããèšèšã¯ãé£ç¶ã¢ã¯ã»ã¹ã§äžæçã«ç©ºãè¿ãåºãããããšããå®å®³ïŒBeyond The Vines, 2026-07-26ïŒããæ¥ãŠããŸããã¿ã€ã ã¢ãŠããæ¥µç«¯ã«çããããšãæ¬åœã¯ååãããã®ã«0ä»¶ãšå€å®ãããŠãã©ã³ãã skip ã«éæ Œãããããšãèµ·ããŸããéã«é·ããããš1åã®å€±æã§æ°ååãŸããŸããå€éš API ãå©ãã¹ã¯ãªããã¯ã1åã®ã¿ã€ã ã¢ãŠãèšå®ããšãäœåãªãã©ã€ãããããšããªãã©ã€éã®åŸ
æ©æéãã®3ã€ãã»ããã§èšèšããŸãã
ãã¹ããã©ã¯ãã£ã¹
ååã»äžæ®µã»ã€ãŸãããã€ã³ããèžãŸããŠãlaunchd ã§åãé·æéã¹ã¯ãªããã®ããã¯ç®¡çã«é¢ããŠå®ã³ãŒãããæœåºãããã©ã¯ãã£ã¹ãæŽçããŸãã
1. pid çæ»ãã§ãã¯ãäž»å€å®ã«ãmtime ãå¯å€å®ã«åºå®ãã
try:
os.kill(pid, 0)
except ProcessLookupError:
pass # å³å¥ªå
except PermissionError:
if age <= LOCK_STALE_SEC:
return False # mtime fallback
else:
if age <= LOCK_STALE_SEC:
return False # mtime fallback
shell ç㯠kill -0 "$(cat pid_file)" ã§åãæå³è«ãæã¡ãŸãïŒautomation-health.sh 23è¡ç®ïŒã
2. 3ã€ã®äŸå€åå²ãå¿ ãã»ããã§å®è£ ãã
ProcessLookupError ã ããæãŸãããš PermissionError ãå€ãžäŒæããŠã¹ã¯ãªãããã¯ã©ãã·ã¥ããŸãã3ã€ãã»ããã§æžããŸãã1ã€ã§ãæ¬ ãããšããã®ã±ãŒã¹å°çšã®ãã°ãã«ãªããŸãã
3. try/finally ã§ããã¯è§£æŸãå¿
ãå
ã
if not acquire_lock():
return 0
try:
# ã¡ã€ã³åŠç
finally:
release_lock()
shell ã§ã¯ trap 'rm -rf "$lock"' EXIT ãåçã§ããæ£åžžçµäºã»äŸå€çºçã»sys.exit() ã©ãã§ãè§£æŸãããŸãã
4. release_lock() 㯠FileNotFoundError ãæ¡ãã€ã¶ã
finally ãšæååŒã³åºããéãªã£ãŠã2åç®ã® unlink() ã§ã¯ã©ãã·ã¥ããªãããã®é²åŸ¡ã§ãããããã¯è§£æŸã®å€±æã§æ¬åŠçãäŸå€ã«ãªããæ¬æ«è»¢åãé²ããŸãïŒ252ã256è¡ç®ã®ãã¿ãŒã³ïŒã
5. LOCK_STALE_SEC ã¯æé·å®è¡æéããéç®ãã
gen_feature.py ã®æé·å®è¡ã¯ CLAUDE_TIMEOUT = 600 ç§ïŒ66è¡ç®ïŒÃ æå€§3åŒã³åºã + ç»å DL ã§çŽ35ã40åã§ããLOCK_STALE_SEC = 2 * 3600ïŒ57è¡ç®ïŒã¯ããã«å¯ŸããŠååãªããŒãžã³ããããŸãããããå€ãçãããŠããkill æžã¿ pid ã2h以å
ã«ããã¯ãææŸããåé¡ã¯è§£æ±ºããŸããããããå€ã®èª¿æŽã¯å¯Ÿççæ³ã§ãããpid çæ»ãã§ãã¯ãæ ¹æ²»ã§ãã
6. ããã¯ãã¡ã€ã«ã®ãã¹ã¯ Path(__file__).resolve() ããŒã¹ã®çµ¶å¯Ÿãã¹ã§çµã¿ç«ãŠã
launchd ã® WorkingDirectory ã«äŸåããªãå®è£
ãå¿
é ã§ããgen_feature.py ã® ROOT = Path(__file__).resolve().parent.parentïŒ36è¡ç®ïŒãããã¹ãŠã®ãã¹ãçµã¿ç«ãŠããã¿ãŒã³ããã®ãŸãŸãã³ãã¬ãŒãã§ãã
7. ããã¯ååŸåã« GEN_WORK.mkdir(parents=True, exist_ok=True) ãå¿
ãå®è¡ãã
ãã£ã¬ã¯ããªãååšããªãååèµ·åã§ã¯ã©ãã·ã¥ããªãããã®å¿
é åŠçã§ããacquire_lock() ã®å
é ã«çœ®ããŸãïŒ226è¡ç®ïŒã
8. shell ã§ã¯ mkdir ããã¯ã§ååæ§ã確ä¿ãã
mkdir ã¯ã«ãŒãã«ã¬ãã«ã§ã¢ãããã¯ã§ãã2ããã»ã¹ãåæã«åŒãã§ãäžæ¹ããæåããŸããïŒautomation-health.sh 22ã27è¡ç®ïŒãPython ã§ãååæ§ãå¿
èŠãªã os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY) ã䜿ããŸãã
9. ã¢ãã¿ãªã³ã°ã¯ããã»ã¹ã§ã¯ãªããæå³ããåºåãã§èšèšãã
automation-health.sh ã® launchd ãã§ãã¯ã¯ last exit=0 ãæ£åžžãšèŠãŸãããããã¯ååŸã¹ãããã exit 0 ã§ãããã³ã³ãã³ãçæãå®äºããããšããæåéç¥ïŒdiscord_post_review(), 973è¡ç®ïŒãäžå®æéå
ã«æ¥ãªããã°ã¢ã©ãŒããåºãããšããæ£æ¹åã®ç£èŠãæ£è§£ã§ãã
10. æåéç¥ãæã€ïŒãšã©ãŒéç¥ã ãã«äŸåããªãïŒ
ãšã©ãŒã ããéç¥ããèšèšã¯ããµã€ã¬ã³ãã¹ããããèŠéããŸãããð o81 IGç¹éçæ: ...ããšããæåéç¥ããæ¥ãªããããšã§ç°åžžãæ€ç¥ããèšèšããä»åã®éå®³ãæ°ã¥ãããå¯äžã®ã·ã°ãã«ã§ããããè¯ãããšãèµ·ããéç¥ããæã€ããšãç°åžžæ€ç¥ã®å®å¹æ§ã«ã€ãªãããŸãã
11. ãã¥ãŒæ®æ°ãè€æ°æ¥å確ä¿ãã
QUEUE_TARGET = 3ïŒ60è¡ç®ïŒãš MAX_GEN_PER_RUN = 2ïŒ61è¡ç®ïŒã®çµã¿åããã¯ãçæã1ã2æ¥é£ç¶ã§å€±æããŠãæçš¿ãéåããªããåšåº«èšèšã§ããããã¯é害ã§1æ¥æ¢ãŸã£ãŠãåšåº«3æ¬ããã°æçš¿ã¯ç¶æã§ããŸãããããã¡ãæã€ããšããèªååã®åäžé害ç¹ãæé€ããŸãã
12. --dry-run ãªãã·ã§ã³ã§ããã¯ãåããã«ç¶æ
確èªã§ããããã«ãã
gen_feature.py --dry-runïŒ981ã993è¡ç®ïŒã¯ããã¯ãåããã«ãã¥ãŒæ®æ°ã ãã衚瀺ããŸããæ¬çªãåããŠããæäžã§ãå®å
šã«åŒã¹ãŸããéå®³èª¿æ»æã«ãããã¯ãåãã«è¡ã£ãŠç«¶åãããå¿é
ãããã«ç¶æ
ã確èªã§ããŸãã
13. --force ãªãã·ã§ã³ã§ E2E ãã¹ããæ¬çªããžãã¯ã®ãŸãŸå®è¡ã§ããããã«ãã
é垞㯠QUEUE_TARGET ãæºãããŸã§ããçæããŸãããã--force ã¯ãã¥ãŒæ®æ°ã«é¢ããã1æ¬çæããŸãïŒ980ã984è¡ç®ïŒãæ¬çªããžãã¯ãå€ããã«ãã€ãã©ã€ã³å
šäœããã¹ãã§ããä¿®æ£åŸã®æ€èšŒã«äœ¿ããŸãã
14. å€éš API ãžã® urllib åŒã³åºãã«ã¯å¿
ã User-Agent ãæç€ºãã
Python ããã©ã«ãã® Python-urllib/x.y 㯠Cloudflare ãäžéš CDN ã«åŒŸãããŸãã"lily-o81-gen/1.0" ã®ããã«èå¥å¯èœãªæååãæå®ããŸããDiscordã»Shopifyã»ãã®ä» Cloudflare é
äžã® API ãå©ãå Žåã¯å¿
é ã§ãã
ãŸãšã
pid 94799 ã2æéããã¯ãå æ ããã®ã¯1è¡ã®æ¬ åŠãåå ã§ãããos.kill(pid, 0) ã§ããã»ã¹ã®çæ»ã確èªããåŠçãæããŠããããã«ãkill æžã¿ã® pid ãããŸã å®è¡äžããšèª€å€å®ãç¶ãããä¿®æ£èªäœã¯æ°è¡ã§ãã
ãããæ¬åœã«çè§£ããªããã°ãªããªãã®ã¯ããã®ããªããã§ãããSIGKILL 㯠finally ãé£ã°ããlaunchd ã® exit 0 ã¯ãæ£ããåãããã§ã¯ãªããããã»ã¹ãçµäºãããã«éããªããã¢ãã¿ãªã³ã°ã¯ããã»ã¹ã®çæ»ã§ã¯ãªããæå³ããåºåãçºçããŠããããã§èšèšããããã®3ç¹ãèªåŸç°å¢ããå£ããåæãšããèšèšãã«ãããšããæå³ã®å®äœã§ãã
æåã®èª€ã£ãä¿®æ£ïŒLOCK_STALE_SEC ã2æéãã30åã«çž®ããïŒã¯ãçç¶ã軜ãããããšããŠæ ¹æ¬åå ãç¡èŠããå
žåçãªèª€ãã§ããã30åã«çž®ããŠããkill æžã¿ pid ã2h以å
ã«ææŸããåé¡ã¯è§£æ±ºããŸããããããæ£åœãªé·æéå®è¡ãéäžã§å¥ªåããããªã¹ã¯ãçã¿ãŸãããCLAUDE_TIMEOUT = 600 ç§ïŒ66è¡ç®ïŒÃ3åŒã³åºãã§æå€§30å以äžãããã¹ã¯ãªããã«30åã®ãããå€ã¯çãããŸãã
èªåŸç°å¢ãäœããšããããšã¯ãã誰ãèŠãŠããªãç¶æ ã§ãæ£ããåãç¶ããã責任ãåŒãåããããšã§ããæ10äžã®å€§åŠçã ã£ãããã¯æ¯æ¥æã§åãããŠããSNSããä»ã¯ launchd + Claude Code ã®èªåŸç°å¢ã§åã£ãŠããŸããæå120äžãç¶æã§ããŠããã®ã¯ãã³ã³ãã³ããã®ãã®ã§ã¯ãªããä»çµã¿ãæ¢ãŸããªãèšèšãã«ç©ã¿äžããŠããä¿®æ£ã®ç©ã¿éãã§ããä»åã® pid çæ»ãã§ãã¯ãããã®äžã€ã§ãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸã ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ