fix(dashboard): supervise next-server for self-heal; sync plugin-root.txt#135
Conversation
📝 WalkthroughWalkthroughDashboard startup now uses a dedicated supervisor that respawns quickly failing dashboard processes and stops them cleanly. Startup timeout handling is simplified, and plugin-root metadata is reconciled from valid links across multiple paths. ChangesDashboard and plugin-root lifecycle
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant dashboard-service.sh
participant dashboard-supervise.sh
participant next-server
dashboard-service.sh->>dashboard-supervise.sh: Start with next binary and port
dashboard-supervise.sh->>next-server: Launch dashboard
next-server-->>dashboard-supervise.sh: Exit
dashboard-supervise.sh->>next-server: Respawn after fast failure
dashboard-service.sh->>dashboard-supervise.sh: Stop process group
dashboard-supervise.sh->>next-server: Terminate child
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@plugin/scripts/dashboard-supervise.sh`:
- Around line 57-62: Replace the external date-based assignments for started and
ended in the dashboard supervision flow with Bash’s built-in SECONDS value,
preserving the elapsed-time calculation used after wait and preventing
zero-duration results when date is unavailable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: cf9f49dc-740a-4a5f-927a-21e1394eba35
📒 Files selected for processing (4)
plugin/scripts/dashboard-service.shplugin/scripts/dashboard-supervise.shplugin/scripts/ensure-plugin-root.shtests/test_install_scripts.py
| started="$(date +%s 2>/dev/null || echo 0)" | ||
| "$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 & | ||
| child=$! | ||
| wait "$child" | ||
| code=$? | ||
| ended="$(date +%s 2>/dev/null || echo 0)" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use Bash's built-in $SECONDS to prevent a potential crash-loop bug and avoid subshells.
If the environment's date command does not support +%s (e.g., in some lightweight environments), the fallback echo 0 causes both started and ended to evaluate to 0. Their difference will always be 0, forcing the supervisor to increment fails on every exit and permanently give up after hitting MAX_FAILS, regardless of how long the dashboard actually stayed up.
Using Bash's built-in $SECONDS tracks elapsed time natively, avoiding external process forks and completely eliminating this failure mode.
♻️ Proposed refactor
- started="$(date +%s 2>/dev/null || echo 0)"
+ started=$SECONDS
"$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 &
child=$!
wait "$child"
code=$?
- ended="$(date +%s 2>/dev/null || echo 0)"
+ ended=$SECONDS📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| started="$(date +%s 2>/dev/null || echo 0)" | |
| "$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 & | |
| child=$! | |
| wait "$child" | |
| code=$? | |
| ended="$(date +%s 2>/dev/null || echo 0)" | |
| started=$SECONDS | |
| "$NEXT_BIN" start -p "$PORT" -H 127.0.0.1 & | |
| child=$! | |
| wait "$child" | |
| code=$? | |
| ended=$SECONDS |
🤖 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 `@plugin/scripts/dashboard-supervise.sh` around lines 57 - 62, Replace the
external date-based assignments for started and ended in the dashboard
supervision flow with Bash’s built-in SECONDS value, preserving the elapsed-time
calculation used after wait and preventing zero-duration results when date is
unavailable.
….txt The dashboard could silently die (a crash, or a macOS jetsam pressure-kill of next-server) with no supervisor, staying down until the next *real* SessionStart hook happened to run `start` again — observed as a ~41-minute outage. Run next-server under a new dashboard-supervise.sh that respawns it within seconds, with a crash-loop guard (bounded consecutive fast failures) and a TERM/INT trap so an intentional `stop` (process-group kill) tears it down without respawning. The now-redundant retry-once block in dashboard-service.sh is removed: the supervisor owns retries, and re-spawning there would start a second supervisor racing for the port. The supervisor also does not fight an intentional stop: a child that exits via SIGTERM (143) — the port reaper in `stop`/reinstall — makes it exit rather than resurrect the dashboard; only crashes and jetsam SIGKILLs respawn. Symmetric in spirit to the backend gap in ReflexioAI#108; the flap predecessor ReflexioAI#111 (EADDRINUSE) was fixed in ReflexioAI#119, and coexistence there is kept. Also fix plugin-root marker drift: on macOS/Linux ensure-plugin-root.sh only ever wrote the symlink, while plugin-root.txt was written by the installer at a different time — so after a version change the file could point at a stale (even deleted) root while the symlink was correct. plugin-root.txt is the fallback consulted by OpenCode's server.mts and by Windows junction consumers. Write it on every symlink rewrite and reconcile it from the link on the no-rewrite exits. Verified: full install-scripts suite green; new tests cover the respawn crash-loop guard and the marker reconcile; live-tested that killing next-server respawns it within ~2s and that a process-group TERM stops the supervisor with no respawn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
598b322 to
fc9c6a5
Compare
|
Closing in favor of an upstream Reflexio OSS fix. Investigation showed the dashboard deaths that motivated this were test-induced (the install-script suite reaps the real :3001 dashboard via a hardcoded stop), not spontaneous. At the claude-smart shell layer, backend and dashboard already recover uniformly on SessionStart on main, so a dashboard-only supervisor was inconsistent and raised supervisor-resilience + cross-platform questions without a matching backend/embedding story. The one real 'dead but not recovering' gap is the local embedding daemon (8072), owned by the Reflexio backend process: |
What's broken
The claude-smart dashboard (Next.js on
:3001) had no process supervisor. When next-server died — a crash, or a macOS jetsam pressure-kill — nothing restarted it until the next real SessionStart hook happened to rundashboard-service.sh startagain, leaving the dashboard down for as long as Claude Code stayed closed (reported as a ~41-minute outage, PID 54172, with the log ending mid-stream and a pid-file tombstone).Separately, the
~/.reflexio/plugin-rootmarkers drift on macOS/Linux.ensure-plugin-root.shrefreshes the symlink on every SessionStart, butplugin-root.txtis written only by the installer, at a different time. After a version change the file can point at a stale — even deleted — root while the symlink is correct. That file is the fallback consulted by OpenCode'sserver.mtsand by Windows junction consumers.What changed
Dashboard self-heal —
plugin/scripts/dashboard-supervise.sh(new)next startin a respawn loop so a silent death self-heals within seconds instead of waiting for the next SessionStart.trap … TERM/INTbreaks the loop so an intentionalstop(process-group kill viaclaude_smart_kill_tree) tears the supervisor and its next-server child down together — no respawn after an intentional stop.HEALTHY_SECSare counted; afterMAX_FAILSconsecutive fast failures the supervisor gives up and defers recovery to the next SessionStart, so a broken build can't tight-loop. All three thresholds are env-tunable.dashboard-service.shnow spawns the supervisor detached as the session leader and records its pid indashboard.pid. The previous retry-once block is removed — the supervisor owns retries, and re-spawning there would start a second supervisor racing for the port.143) — the port reaper instop/reinstall, when it can't see this supervisor's pid — the supervisor exits rather than resurrecting the dashboard. Only crashes and jetsamSIGKILLs are respawned. (A stop that does see our pid group-kills the supervisor via the TERM trap.)Plugin-root marker sync —
plugin/scripts/ensure-plugin-root.shplugin-root.txton every symlink rewrite, and reconcile it from the link on the two no-rewrite exit paths (cache-tracking-unchanged and valid-link self-heal), so the file always matches the symlink.Scope / relation to existing issues
Testing
143);plugin-root.txtreconcile from a stale value on a valid link. The log-redirection assertion is updated for the new spawn line.tests/test_install_scripts.pyruns green locally.HTTP 200; a process-groupTERMstops the supervisor with no respawn.🤖 Generated with Claude Code
Summary by CodeRabbit