Conversation
`daemon::run` opened every agent and only then bound the socket, so the whole boot sat inside a window where `is_socket_live` still said no daemon was coming. A second one started in that window — by `ensure_daemon`, or by hand — passed the same check, spawned a duplicate of every agent into the same cwd, and raced for the same path. The loser returned through `?` from the bind, never reaching `shutdown_agents`, and left its children writing to the winner's transcripts. Binding first is the claim on the path, so the second daemon now bails before it opens anything. That leaves a window of the opposite kind: bound, but not yet accepting. Returning from `ensure_daemon` on the bind would hand back a socket that takes the connection and answers nothing until the roster is open — on the desktop that is a main-thread command blocking on a read. So readiness is its own signal. `daemon.version` is written when the accept loop is up and cleared at boot, which makes it mean "serving" rather than "was serving at some point", and `daemon_is_ready` pairs it with the socket. A caller that finds a bound socket with no version file waits for the boot in progress instead of judging its version — the old code read the missing file as a mismatch and stopped a daemon that was still starting. Teardown moved out of the accept loop's tail into `run`, so every way out clears the socket, the pid and the version. It only ran on a clean shutdown before, which is how a failed boot could leave a socket that answers `is_socket_live` for a daemon that is gone.
5 tasks done
im-ian
marked this pull request as draft
September 21, 2026 04:57
Owner
Author
|
리뷰 결과 머지 보류. 이 PR 의 핵심 주장("bind 가 소유권 주장")이 성립하지 않아요.
근본 원인은 따로 있어요: 데몬 기동에 상호배제가 없고, |
This was referenced Sep 21, 2026
Owner
Author
This was referenced Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
데몬이 소켓을 bind 하기 전에 봇을 전부 spawn 해서, 그 사이에 두 번째 데몬이 들어와 같은 로스터를 또 띄울 수 있었어요. bind 를 맨 앞으로 옮기고, "준비됨" 신호를 따로 뒀어요.
원인
daemon::run()의 순서가 이랬어요:로스터가 큰 경우 그 구간이 몇 초예요.
client::ensure_daemon은 4초 deadline 안에 소켓이 안 보이면 실패로 치고 빠지고, 다음 호출이 또 들어와요. 두 데몬이 각자 모든 봇을 같은 cwd 에 spawn 하고 같은 transcript 에 써요.bind 에서 진 쪽은
?로 바로 return 해서shutdown_agents()에 도달하지 못해요. 그래서 죽은 데몬의 자식 CLI 들이 그대로 남아 이긴 데몬의 파일에 계속 써요.고친 것
bind 를 맨 앞으로 — 경로에 대한 소유권 주장이 곧 bind 예요. 두 번째 데몬은 아무것도 열기 전에 bail 해요.
"준비됨"을 별도 신호로 — bind 를 앞당기면 반대 구간이 생겨요: 묶였지만 아직 accept 안 함. 여기서
ensure_daemon이 반환하면 호출자는 연결은 되는데 응답은 없는 소켓을 받아요 (데스크톱에선 메인 스레드 command 가 read 에서 멈춤).daemon.version은 accept 루프가 뜬 뒤 쓰고, 부팅 시작 때 지워요 → "지금 서비스 중" 을 뜻하게 돼요.paths::daemon_is_ready()= 버전 파일 + 소켓.ensure_daemon은 ready 를 기다려요. 소켓은 살아 있는데 버전 파일이 없으면 = 누가 부팅 중 → 판단하지 말고 기다려요. 기존 코드는 없는 파일을 "버전 불일치" 로 읽고 부팅 중인 데몬을 죽였어요.teardown 을
run()으로 — accept 루프 꼬리에 있던 정리를 밖으로 빼서, 어떤 경로로 끝나든 소켓·pid·버전을 지워요. 기존엔 정상 종료에서만 돌아서, 부팅 실패가 "죽은 데몬을 가리키는 살아있는 소켓" 을 남겼어요.검증
cargo test --locked -p crew— 213 passeda_daemon_leaves_nothing_behind_when_it_stops— 실제 데몬을 띄우고Shutdown보낸 뒤 소켓/pid/버전 부재 확인. 뮤테이션 확인: teardown 3줄을 지우면FAILEDa_bound_socket_stops_a_second_daemon_before_it_spawns— bind 만 된(accept 전) 소켓도is_socket_live가 yes 로 읽는다는, 이 수정이 기대는 전제를 고정cargo fmt --check— 새 코드 clean