fix(app): stabilize daemon lifecycle setup#2177
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe hook centralizes lifecycle state into a ref (latestLifecycleRef), updates callbacks/effects to read from it to avoid stale closures, and adds tests validating retry scheduling and that lifecycle setup/cleanup runs only once across state updates. ChangesDaemon lifecycle stability fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/src/hooks/useDaemonLifecycle.ts (1)
96-96: 💤 Low valueConsider adding an eslint-disable comment for clarity.
The empty dependency array is intentional since the callback reads from
latestLifecycleRef.current. Adding a brief comment would help future maintainers understand this is deliberate and not an oversight.📝 Suggested documentation
- }, []); + // Dependencies intentionally empty - reads current state from latestLifecycleRef + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/hooks/useDaemonLifecycle.ts` at line 96, In useDaemonLifecycle, the empty dependency array on the effect is intentional because the effect reads from latestLifecycleRef.current; add a brief inline eslint-disable comment (e.g., // eslint-disable-next-line react-hooks/exhaustive-deps — intentional: uses latestLifecycleRef) or a short explanatory comment above the useEffect to clarify why the dependency array is empty so future maintainers understand this is deliberate in useDaemonLifecycle and not an oversight.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/src/hooks/useDaemonLifecycle.ts`:
- Line 96: In useDaemonLifecycle, the empty dependency array on the effect is
intentional because the effect reads from latestLifecycleRef.current; add a
brief inline eslint-disable comment (e.g., // eslint-disable-next-line
react-hooks/exhaustive-deps — intentional: uses latestLifecycleRef) or a short
explanatory comment above the useEffect to clarify why the dependency array is
empty so future maintainers understand this is deliberate in useDaemonLifecycle
and not an oversight.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4d8a6430-d5f6-4939-98dd-44c3f11248b5
📒 Files selected for processing (2)
app/src/hooks/__tests__/useDaemonLifecycle.test.tsapp/src/hooks/useDaemonLifecycle.ts
7cdee57 to
6a7494f
Compare
graycyrus
left a comment
There was a problem hiding this comment.
Review — graycyrus
Clean fix for the daemon lifecycle setup/cleanup loop (#2151). The ref-based pattern (latestLifecycleRef) is the standard React approach for stabilizing callbacks that depend on frequently-changing state — well applied here.
What changed
| File | Summary |
|---|---|
useDaemonLifecycle.ts |
Destructure startDaemon directly; add latestLifecycleRef syncing all lifecycle state; callbacks read from ref instead of closures; dependency arrays reduced to stable values |
useDaemonLifecycle.test.ts |
Two new tests: retry scheduling + lifecycle stability (setup/cleanup runs exactly once across state transitions) |
Verification
- Issue alignment: All acceptance criteria from #2151 addressed — loop eliminated, CPU stable, lifecycle idempotent, cleanup scoped to unmount, daemon behavior preserved, regression tests added, coverage gate passes.
- Effect dependency analysis: Main effect re-runs only on
isAutoStartEnabled/uidchanges (installation-level inputs).attemptAutoStartandhandleVisibilityChangeare stable refs. Correct. - Race safety:
scheduleRetryclears existing timeout before scheduling new one — no concurrent retry risk. - CI: All checks green including coverage gate.
No findings beyond what CodeRabbit already noted. LGTM — moving to to-be-approved/.
…mansai#2128) CodeRabbit on PR tinyhumansai#2256 flagged that the inline default array literal `['read', 'write']` for `capabilitiesOnSuccess` creates a fresh reference on every parent render. Since that prop is in the effect's dep array, the global `oauth:success` / `oauth:error` listeners would tear down and re-subscribe on every render of any panel using the default — same class of bug as tinyhumansai#2177. Hoist to a module-level `DEFAULT_OAUTH_CAPABILITIES` constant so the identity is stable. No behaviour change at the call sites. Co-Authored-By: Claude <noreply@anthropic.com>
Fixes #2151.
Root cause: the daemon lifecycle setup effect depended on callbacks that were recreated when daemon health state changed. During startup/recovery, status, recovery, and attempt updates could therefore tear down and reinstall the lifecycle listener/timers, producing repeated setup/cleanup logs.
This patch keeps the listener/timer setup stable across ordinary daemon state updates by reading the latest lifecycle state through a ref. The setup effect can still restart when installation-level inputs change, such as the user id or auto-start setting.
Validation:
Summary by CodeRabbit
Tests
Improvements