확인 끝났습니다. 코드·프로세스·git 상태를 직접 검증한 결과로 절차를 드립니다. 추측이 아니라 지금 이 머신의 실제 상태 기준입니다.
결론 먼저 (진단)
지금 LAX daemon이 2개 떠 있습니다: PID 18220, 28044. 각각 다른 run.bat 루프(cmd.exe)가 부모입니다 → run.bat 자동재시작 루프가 2개 돌고 있습니다. lock(.daemon.lock)은 28044만 가리킵니다.
이게 "재시작 오류 반복"의 진짜 원인입니다. node 하나만 죽여도 다른 루프가 30초 뒤 옛 코드로 되살립니다. 그래서 죽인 줄 알았는데 옛 코드가 계속 뜨는 것. 핵심 교훈: node 프로세스가 아니라 run.bat 루프(부모 cmd)부터 없애야 합니다.
좋은 소식: 디스크 코드는 이미 99dca83이고 작업트리도 clean입니다. git pull도 빌드도 필요 없습니다(daemon은 순수 node, run.bat = node daemon.js). 그냥 깨끗이 한 번만 재시작하면 #1·#2가 반영됩니다.
아래 명령은 전부 Git Bash에 그대로 붙여넣으면 됩니다.
1. 현재 어떻게 떠 있는지 + 확인 명령
실행 방식: start-hidden.vbs → (숨김) run.bat 무한루프 → node daemon.js. 종료하면 run.bat이 30초 뒤 재시작.
상태 확인 (이 명령을 절차 내내 재사용 — "데몬 목록"이라 부르겠습니다):
powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | ? { \$_.CommandLine -like '*daemon.js*' } | Select ProcessId,ParentProcessId,CreationDate | Format-Table -Auto"
cat /c/Users/user/LAX-site/daemon/.daemon.lock
- 정상은 1줄(=1개). 지금은 2줄로 나옵니다(=중복, 비정상).
2. 안전하게 중지 (in-flight 태스크 안 끊기게)
타이밍은 수동으로 잴 필요 없습니다. .shutdown 플래그를 떨구면 daemon이 진행 중 태스크를 끝내고 idle이 됐을 때만 스스로 종료합니다(daemon.js 1870~1875 검증함). 순서가 중요합니다 — 먼저 자동재시작 루프부터 죽여야 drain 후 안 되살아납니다.
2-a. run.bat 루프(부모 cmd) 종료 — 자동재시작 차단. (node 자식은 안 죽으니 진행 중 태스크는 계속됩니다)
powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | ? { \$_.CommandLine -like '*daemon.js*' } | % { Stop-Process -Id \$_.ParentProcessId -Force -EA SilentlyContinue }"
2-b. graceful drain — 진행 중 태스크 마치고 스스로 종료.
touch /c/Users/user/LAX-site/daemon/.shutdown
그다음 "데몬 목록"(1번)을 5초 간격으로 다시 실행해 줄이 0개가 될 때까지 기다립니다. (태스크가 돌고 있으면 그게 끝날 때까지 남아 있는 게 정상)
2-c. (예외) ~90초가 지나도 안 빠지면 = 멈춘 태스크. 강제 종료해도 됩니다 — 미완 태스크는 lease 만료 후 서버가 자동 재실행하므로 데이터 유실 아닙니다(코드/런북 확인함):
powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | ? { \$_.CommandLine -like '*daemon.js*' } | % { Stop-Process -Id \$_.ProcessId -Force }"
2-d. "데몬 목록"이 0개인 것을 확인한 뒤 잔여 플래그/락 정리:
rm -f /c/Users/user/LAX-site/daemon/.shutdown /c/Users/user/LAX-site/daemon/.daemon.lock
⚠️ .daemon.lock 삭제는 반드시 데몬 목록 0개일 때만. 살아있는데 지우면 중복 부팅을 허용합니다.
3. 정확한 시작 (99dca83 보장)
3-a. 먼저 코드가 99dca83인지 확인 (이게 보장의 핵심 — pull/빌드 불필요):
cd /c/Users/user/LAX-site && git rev-parse --short HEAD && git status --porcelain daemon/daemon.js
→ 99dca83 출력 + 둘째 줄 빈 줄이면 OK(디스크 코드 = 99dca83).
만약 99dca83이 아니면 그때만: git checkout 99dca83.
3-b. 시작 — 우선 포그라운드로 띄워 로그를 눈으로 봅니다(과거 오류를 바로 잡으려면 이게 안전):
cd /c/Users/user/LAX-site/daemon && node daemon.js
정상 부팅 신호:
[daemon] Starting — ID: ...가 뜨고Another daemon already holds the lock — exiting이 안 뜨면 성공(이게 뜨면 2번 중지가 덜 된 것).
이 창을 켜둔 채 5번 검증을 합니다.
3-c. 검증 끝나면 영구(자동재시작) 모드로 전환: 위 창에서 Ctrl-C 로 끈 뒤:
cd /c/Users/user/LAX-site/daemon && wscript start-hidden.vbs
⚠️ 포그라운드(3-b)와 vbs(3-c)를 동시에 켜지 마세요 — singleton lock 때문에 한쪽이 죽고 그 run.bat이 30초마다 헛돌게 됩니다. 반드시 Ctrl-C 후 vbs.
4. 과거 자주 난 오류 + 대처
| 오류 | 증상 | 회피/대처 |
|---|---|---|
| 중복 인스턴스 (지금 상태) | daemon.js 2개+, 같은 태스크 이중 처리, 재시작해도 옛 코드가 계속 뜸 | node만 죽이지 말고 부모 cmd(run.bat 루프)부터 죽인다 (2-a). 시작 전 "데몬 목록" 0개 확인 |
| 부팅 직후 즉시 종료 | run.bat 창이 떴다 ...already holds the lock — exiting 후 30초마다 반복 |
살아있는 다른 daemon이 있다는 뜻. 2번을 끝까지(목록 0개) 한 뒤 시작. 필요시 죽은 PID 남은 .daemon.lock 삭제(2-d) |
| 강제 kill로 태스크 끊김 | 진행 중 에이전트 런이 중단/거짓 done | 항상 .shutdown drain 우선(2-b). 강제킬은 안 빠질 때만 — lease 만료 후 재실행되니 유실 아님 |
| (보너스) env 미설정 | 부팅 즉시 BRIDGE_BASE_URL and BRIDGE_SECRET are required 후 종료 |
반드시 daemon/ 디렉터리에서 시작(.env.wikicomu 로드). 3-b 명령이 그렇게 돼 있음 |
5. #1·#2 반영 검증 (자가 DONE 금지 — 실물로 확인)
seedAgentSkills(line 874)와 distill 주입(line 885)은 같은 spawn 블록이고, persona에 합쳐져 --system-prompt로 들어갑니다(line 947~948, 확인함). 따라서 .seeded 마커가 새로 생기면 그 블록이 실행됐다는 뜻 = #1·#2 둘 다 주입됨입니다.
현재 baseline: .seeded 마커 0개 (확인함). 그래서 검증이 깔끔합니다.
5-a. 재시작 후 아무 에이전트에게 태스크 1건을 실제로 던집니다 (LAX 채팅/활동에서 누구든 1회 실행). fresh 슬러그면 첫 실행에 시딩이 돕니다.
5-b. #1 시딩 확인:
find /c/Users/user/LAX-site/daemon/agent-skills -name .seeded
cat /c/Users/user/LAX-site/daemon/agent-skills/*/.claude/skills/.seeded
→ seeded 2026-... copied=N skipped=M 한 줄이 보이면 성공. 포그라운드 데몬 로그(3-b 창)에도 copied>0이면 [daemon] seeded skills for <slug>: copied=... 가 찍힙니다.
5-c. #2 distill 주입 확인:
- 코드 증거(실행 바이너리에 들어있음):
grep -c "distill-on-done" /c/Users/user/LAX-site/daemon/daemon.js>=1+ 3-a의HEAD=99dca83/clean → 지금 도는 데몬이 정확히 이 코드. - 동작 증거: 5-a의 그 태스크가
.seeded를 만들었다 = 같은 블록이 실행됐다 = 그 에이전트의 시스템 프롬프트에 distill 가이드가 붙었다는 직접 증거. 실제로 그 에이전트는 답 끝에서distill-on-done절차를 1회 수행합니다(해당 에이전트의 /activity 출력에서 관찰 가능).
주의: 마커는
copied=0(전부 충돌)이어도 생성됩니다. 즉 마커 존재 = 훅 실행이 판정 기준이지, copied 숫자가 0이라고 실패가 아닙니다.
6. 롤백 (문제 시)
99dca83이 HEAD이고 그 위 커밋이 없어 revert는 깨끗합니다(되돌리는 새 커밋 생성, 1번으로 원복 가능):
cd /c/Users/user/LAX-site && git revert --no-edit 99dca83
그다음 2번(중지) → 3번(시작) 을 다시 수행하면 #1·#2 없는 상태로 복귀합니다. revert는 새 커밋이라 언제든 git revert <revert커밋>으로 다시 되살릴 수 있습니다(reset --hard 쓰지 마세요).
한 줄 요약: ① 데몬 목록 확인(지금 2개=비정상) → ② run.bat 루프(부모)부터 죽이고 .shutdown drain → 목록 0개 + 락 정리 → ③ 99dca83 확인 후 포그라운드 node daemon.js로 띄워 로그 확인 → ④ 태스크 1건 던져 .seeded 생성 확인(= #1·#2 반영 증거) → ⑤ Ctrl-C 후 wscript start-hidden.vbs로 영구 모드 → 문제 시 git revert 99dca83.
필요하면 이 절차를 daemon/RUNBOOK-emergency-recovery.md에 정본으로 추가해 두겠습니다(리아와 이력 정리). Jason이 지금 바로 쓰려면 위 블록들을 순서대로 붙여넣으면 됩니다.