Repository navigation
Open the window before the daemon. - #19
Merged
Merged
Conversation
im-ian
force-pushed
the
window-before-daemon
branch
from
September 21, 2026 03:25
1caa221 to
7531fb5
Compare
`desktop::run` returned the daemon's start error before building anything, so a daemon that would not start took the whole app with it. From Finder that reads as the icon bouncing once and nothing opening — the same symptom for a config this binary cannot parse, a socket it cannot bind, or any reason yet to come, with no window left to fix it from. The start still happens there, and still once: the window is built whether or not it worked. Leaving it where it is keeps every command that goes straight to `client::rpc` finding a daemon the way it always has, and keeps the start off the poll — `ensure_daemon` waits out a start deadline, `daemon::run` opens every agent before it binds, and the twelve commands that call `ensure_daemon` from the main thread all assume it is rare. `run` calls `ensure_home` itself now. It was reached through `ensure_daemon`, and `setup` writes `ui.pid` into that directory and swallows the failure — on a first launch the pid would go unwritten and the window would notify as if it were closed. A window with no daemon could only report `cannot connect to <socket>`, which describes the hole and not what made it, so the start failure is kept and `daemon_ping` answers with it. `Event::Error` carries its own message now too, the way every sibling command already returns it. The reason reaches the user through a banner in the grid row beside `UpdateBanner`, which pushes the thread down rather than covering it. `connected` and `connDetail` have been computed since the poll was written and never rendered, so nothing yet depended on who set them: `showError` marked the daemon down for a blank bot name, and `tickMessages` overwrote the reason every 200ms. The poll owns them.
im-ian
force-pushed
the
window-before-daemon
branch
from
September 21, 2026 03:38
7531fb5 to
63cba96
Compare
From review of #19. `ensure_home` still used `?`, so a home directory that cannot be created exited with no window — the one thing this PR claims cannot happen any more. It records its reason like the daemon start does, and names the path it could not create. The recorded reason was never dropped. A daemon started later, by hand or by any command that calls `ensure_daemon`, left it in place; a disconnect hours afterwards then blamed a config file the user had already fixed. `daemon_ping` clears it as soon as a daemon answers. Poisoning the mutex threw the reason away and left the bare socket error this exists to replace. `unwrap_or_else(|e| e.into_inner())`, the way `paths::testing` already does it. A bundled .app discards stderr, so the reason also goes to `crew.log`. `daemon_ping` returns `()`. Every non-Pong outcome is an error now, so the bool advertised a false case that could not happen — which is what let the old `Ok(_) => Ok(false)` read as a live connection. The banner leads with a line in the user's language; the daemon's own English text and log tail follow it, and are left out entirely when empty rather than painting a bare red bar. `tickMessages` logs what it catches — `get_messages` answers `Error` for a removed agent too, and swallowing that froze the thread on stale messages with no trace.
5 tasks done
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
데몬이 어떤 이유로든 못 뜨면 앱이 창도 없이 종료되던 걸, 창은 무조건 띄우고 실패 이유를 창 안에서 보여주도록 바꿔요. #17 이 원인 하나(빈 로스터)를 없앴다면, 이건 증상 자체를 없애요.
증상
agents.json이 깨져 있으면 Finder 에서 아이콘만 한 번 튀고 아무것도 안 열려요. #17 머지 후에도 그대로 재현돼요.원인
desktop::run()이tauri::Builder보다 먼저client::ensure_daemon()?를 호출하고, 실패하면main()이exit(1)— 창이 만들어지기 전이에요. 다른 호출부는 전부 에러를 UI 에 문자열로 넘기는데run()만 프로세스를 죽여요.고친 것
창은 무조건 뜬다
ensure_daemon()과ensure_home()둘 다 에러를 반환하지 않고 기록만 해요.run()안에서는 더 이상?를 쓰지 않아요.run()에서, 여전히 한 번 — 창만 결과와 무관하게 만들어져요.ensure_home을 직접 부르는 이유: 홈 디렉터리를 만들던 게ensure_daemon()이었고,setup의write_ui_pid()는 실패를 삼켜요. 첫 실행에서ui.pid가 안 써지면 창이 열려 있는데도 OS 알림이 떠요.이유를 들고 있다가, 해결되면 잊는다
cannot connect to <socket>밖에 말할 수 없어요. 구멍이 있다는 말이지 왜 생겼는지가 아니에요. 시작 실패 사유를 저장해 뒀다가daemon_ping이 그걸로 답해요.unwrap_or_else(into_inner)—paths::testing의 기존 패턴)..app은 stderr 를 버려서, 사유를crew.log에도 남겨요.daemon_ping이Result<()>를 반환해요. Pong 이 아닌 모든 결과가 에러라bool의false는 도달 불가였고, 그게 예전Ok(_) => Ok(false)가 "정상 연결"로 읽히던 원인이에요.창 안에서 보여준다
ConnectionBanner를UpdateBanner옆 grid row 에 놓아요 — 덮지 않고 아래로 밀어내요. 슬롯은 항상 마운트돼 있어서role="status"가 실제로 읽혀요.conn.down, 한/영), 데몬의 영문 원문과 로그 tail 은 그 아래에. 원문이 비어 있으면 아예 안 그려요 (빈 빨간 막대 방지).connected === false로 판정해요. 초기값이null(아직 확인 전)이에요.connDetail/connected는 폴링만 씁니다.showError가 봇 이름 빈칸 같은 로컬 검증 실패에도 데몬이 끊긴 걸로 만들고 있었고,tickMessages가 200ms 마다 진짜 이유를 덮어썼어요. 지금껏 아무 데도 렌더 안 돼서 안 보였을 뿐이에요.tickMessages는 잡은 에러를console.error로 남겨요.get_messages는 삭제된 봇에도Error로 답해서, 통째로 삼키면 스레드가 옛 메시지에 멈춘 채 흔적이 없어요.검증
cargo test --locked -p crew— 211 passed (ping_failure단위 테스트 포함: 기록된 사유가 소켓 에러를 이기고, 클리어 후엔 안 이김)npm test— 130 passed /npm run build(tsc 포함) 통과cargo fmt --check—desktop.rscleancrew daemon프로세스 0개EXITED (no window)남긴 트레이드오프
ensure_daemon()은 여전히Builder앞에서 동기로 돌아요. 정상 실행에선 즉시 반환하고, 데몬이 안 뜰 때만 최대 4초 지연 뒤 창이 떠요. 백그라운드 스레드로 옮기면 창은 즉시 뜨지만,client::rpc로 직행하는 command 들(send_message,get_messages등 ~15개)이 데몬이 준비되기 전에 실패할 수 있어요. 4초 뒤 쓸 수 있는 창 과 즉시 뜨지만 잠깐 깨진 창 중에 전자를 택했어요.자동 재연결도 뺐어요. 설정을 고친 뒤엔 앱 재시작이 필요해요 — 폴링에서 데몬을 띄우는 접근은 메인 스레드 blocking, 데몬 중복 기동, 좀비 누적을 불러서 접었어요 (아래 참조).
범위 밖 (리뷰에서 나온 기존 문제)
list_agents등 12개 command 가 sync 인 채로ensure_daemon()을 호출해요. 데몬이 없는 창에서 "새 봇" 같은 동작을 하면 4초간 UI 가 멈춰요. 이 PR 이 그 상태를 도달 가능하게 만들지만, 고치려면 12곳을 async 로 옮겨야 해서 따로 가는 게 맞아요.ensure_daemon의cmd.spawn()이Child를 reap 하지 않아요.crew.log에 회전이 없어요.daemon::run이 소켓을 bind 하기 전에 전체 로스터를 spawn 해요.