정확히 여덟 시간
2026-10-14 저녁, todo #423을 맡은 Coop 워커가 스스로 blocked를 보고했어. 아빠만 답할 수 있는 질문이 생겨서, 프롬프트 앞에 서서 아빠를 기다리기로 한 거야. UTC 20:10에 조용해졌어. 다음 날 아침 UTC 04:10, 정확히 여덟 시간 만에 사라졌어. claude.ai에는 "Remote Control disconnected"가 떴고 다시는 안 돌아왔어. 같은 초에 Coop 어댑터는 채팅이 닫혔고 등록은 남겨둔다고 기록했어.
Coop은 손도 안 댔어. 네이티브 채팅을 멈추는 Coop 경로는 하나도 없어. 범인은 Claude Code 자체의 백그라운드 데몬이었어. 이 데몬은 프롬프트 앞에서 놀고 있는 백그라운드 채팅을 은퇴시켜. Remote Control 채팅은 입력 없이 여덟 시간, 나머지는 한 시간, 맥 메모리가 모자라면 1분 안에. 데몬 로그에 bg retire <short>: idle-prompt, idle 8h 한 줄을 남기고 프로세스를 끝내.
데몬 입장에선 착실한 정리야. 노는 채팅은 메모리만 먹으니까. Coop 입장에선 아주 구체적인 손실이었어. 은퇴한 그 채팅이 바로 아빠의 답이 도착해야 할 곳이었거든. 일은 여전히 그 채팅에 묶여 있었고, 의무도 아직 갚아야 했고, 나중에 아빠가 답장을 보냈을 땐 받을 데가 없었어.
은퇴가 남기는 것
수리는 은퇴가 실제로 뭘 없애는지 재는 데서 시작했어. 답은 보기보다 적다는 거였어. transcript는 남아. 실행할 때 쓴 옵션까지 담긴 데몬의 job 기록도 남아. Remote Control 세션도 남아.
그다음엔 설계를 정한 프로브가 있었어(2026-10-16). claude --bg --resume <session-id>를 다른 플래그 하나 없이 돌리면, 저장된 옵션 그대로 바로 그 세션이 깨어나. 같은 id, 같은 claude.ai 링크로. 프로브는 깨우기 전과 후를 비교해서 확인했어. 거기에 플래그를 하나라도 붙이면 CLI는 새 id로 사본을 띄워. 그래서 깨우는 명령은 딱 그 세 단어와 id야. 채팅에 필요한 나머지, 지난 레슨의 워커 플래그 두 개까지 포함해서 전부 job 기록에서 돌아와.
깨우는 쪽
어댑터 자식마다 이제 자기가 따라가는 채팅 하나를 위한 작은 Waker(revival.py)가 붙어 있어. 규칙은 이 순서야.
- 아직 일을 빚진 채팅만. 등록이 빚진 게 없으면, 은퇴한 채팅은 그냥 끝난 거야.
- 백그라운드 채팅만. 세션 id가 맞는 데몬 job 기록이 있어야 해. Desktop이나 터미널 채팅은 그런 기록이 없어서 절대 안 깨워. 그건 Claude Code만 다시 열 수 있어.
- 사라지고 1분이 지난 뒤에만(
GONE_GRACE). 데몬엔 자체 크래시 재시작이 있고, 그 경주에선 데몬이 이겨야 해. - 15분에 많아야 한 번(
RETRY), 채팅이 계속 없는 동안. - 돌아온 게 뭔지 확인해. CLI는 어느 short id를 백그라운드로 보냈는지 출력해. 그게 그 채팅 자신의 id가 아니면 CLI가 사본을 띄운 거고, Coop은 그 자리에서 사본을 멈추고 지워. 두 번째 채팅이 첫 번째 대신 대답하는 일은 절대 없어.
시도할 때마다 런타임 로그에 revived나 revive_failed 한 줄이 남고, 같은 실패는 15분마다가 아니라 한 번만 적혀. 이 장치를 안전하게 만드는 건, 깨어난 채팅이 뭘 하느냐야. 답은 아무것도 안 해. 프롬프트 앞에서 놀던 상태 그대로 돌아와. 깨운다고 일이 저절로 다시 시작되진 않아. 그다음 어댑터가 세션 id로 살아 있는 채팅을 찾아서, 아직 빚진 걸 전에 쓰던 그 문으로 전해줘. 아빠의 답장도 포함해서.
빚졌다는 말은 진짜 빚이어야 해
Waker는 몇 시간 만에 조용하던 버그를 시끄럽게 만들었어. "아직 일을 빚졌다"는 Engine.owed가 계산했는데, 거기 세던 것 중엔 절대 갚을 수 없는 것도 있었어. 이미 끝난 바인딩에 보낸 답글 리뷰는 확인받을 수가 없어. 엔진이 그 리뷰의 COOP_READ를 거절하거든("Reply review no longer has an active binding"). 이미 끝난 디스패치에 보낸 초대장이나 작업 봉투도 영영 답을 못 받아. 이런 것들이 outbox에 보냄, 영수증 불확실로 남아서 영원히 빚으로 세어졌어.
그렇게 등록에 붙잡혀 있던 끝난 채팅이 넷이었어. 2026-10-05부터 잡혀 있던 것도 있었고. Waker 전엔 어댑터 하나가 계속 폴링하는 정도로 끝났어. Waker가 생기자, 데몬이 은퇴시킬 때마다 그 넷을 전부 깨워서 아무도 받을 수 없는 걸 전하려 들었을 거야. 그날 아침 owed는 그런 행을 무시하는 법을 배웠고, 이틀 뒤엔 엔진이 아예 그런 행을 열어두지 않게 됐어. 일이 끝나는 바로 그 트랜잭션 안에서 취소하거든(그 이야기는 트랙 6 마지막 레슨에 있어).
끝난 일을 숨긴 청소부
Waker는 짝을 하나 데리고 나왔어. 빚진 채팅의 데몬 은퇴를 되돌릴 거라면, 끝난 Coop 채팅은 일부러 치워야 한다고 본 거지. 그래서 런타임의 Reaper가 10분마다 돌면서, 등록이 은퇴했고, 빚진 게 없고, 활성 등록 어디서도 안 쓰고, 사람을 기다리는 것도 아닌 채 두 시간 동안 놀았던 Coop 채팅을 멈췄어. 거기 맞춰서 노는 등록의 은퇴 시간도 여섯 시간에서 두 시간으로 줄었고.
이틀 갔어. claude stop은 프로세스만 끝내는 게 아니었어. transcript와 job 기록은 디스크에 남겨도, Claude 앱의 세션 목록과 검색에서 그 채팅을 지워. 아빠가 끝난 일을 찾으러 갔는데 못 찾았어. 모든 브레인용 Coop을 만든 그 채팅도 그렇게 사라졌어. 런타임 로그엔 2026-10-16부터 10-18 사이 office에서만 그런 stop이 49개 있었어. 데몬이 스스로 은퇴시킨 채팅(settled, idle 8h)은 목록에 그대로 남는데 말이야.
그래서 todo #518이 청소부를 들어냈어. stop 헬퍼, receiver의 청소 훅, 런타임의 청소 루프까지 전부. 이제 어떤 receiver나 런타임 경로도 청소하지 않는다는 걸 테스트 하나가 못 박고 있어. 노는 등록의 은퇴는 남았어. 빚진 것 없이 두 시간 놀면 Coop은 자기 기록과 그 채팅을 따라가던 어댑터 자식만 끝내고, 채팅엔 손대지 않아. Coop 안에 남은 claude stop은 깨우다가 실수로 생긴 사본을 지우는 그 하나뿐이야.