-
Notifications
You must be signed in to change notification settings - Fork 990
feat(desktop): restart daemon from terminal-unreachable toast #4962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||
| import { toast } from "@superset/ui/sonner"; | ||||||||||||||||||
| import type { FitAddon } from "@xterm/addon-fit"; | ||||||||||||||||||
| import type { SearchAddon } from "@xterm/addon-search"; | ||||||||||||||||||
| import type { Terminal as XTerm } from "@xterm/xterm"; | ||||||||||||||||||
|
|
@@ -250,6 +251,25 @@ export const Terminal = memo(function Terminal({ | |||||||||||||||||
| // Auto-retry connection with exponential backoff | ||||||||||||||||||
| const retryCountRef = useRef(0); | ||||||||||||||||||
| const MAX_RETRIES = 5; | ||||||||||||||||||
| const daemonFailureToastShownRef = useRef(false); | ||||||||||||||||||
|
|
||||||||||||||||||
| const restartDaemonMutation = electronTrpc.terminal.restartDaemon.useMutation( | ||||||||||||||||||
| { | ||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||
| toast.success("Daemon restarted", { | ||||||||||||||||||
| description: | ||||||||||||||||||
| "All sessions killed and daemon restarted. The app will use a fresh daemon.", | ||||||||||||||||||
| }); | ||||||||||||||||||
| }, | ||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||
| toast.error("Failed to restart daemon", { | ||||||||||||||||||
| description: error.message, | ||||||||||||||||||
| }); | ||||||||||||||||||
| }, | ||||||||||||||||||
| }, | ||||||||||||||||||
| ); | ||||||||||||||||||
| const restartDaemonMutationRef = useRef(restartDaemonMutation); | ||||||||||||||||||
| restartDaemonMutationRef.current = restartDaemonMutation; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Stream handling | ||||||||||||||||||
| const { handleTerminalExit, handleStreamError, handleStreamData } = | ||||||||||||||||||
|
|
@@ -272,9 +292,28 @@ export const Terminal = memo(function Terminal({ | |||||||||||||||||
|
|
||||||||||||||||||
| // Auto-retry when connection error is set | ||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||
| if (!connectionError) return; | ||||||||||||||||||
| if (!connectionError) { | ||||||||||||||||||
| daemonFailureToastShownRef.current = false; | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (isExitedRef.current) return; | ||||||||||||||||||
| if (retryCountRef.current >= MAX_RETRIES) return; | ||||||||||||||||||
| if (retryCountRef.current >= MAX_RETRIES) { | ||||||||||||||||||
| if (!daemonFailureToastShownRef.current) { | ||||||||||||||||||
| daemonFailureToastShownRef.current = true; | ||||||||||||||||||
| toast.error("Terminal daemon unreachable", { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Multiple terminal panes will each show their own "Terminal daemon unreachable" toast since Prompt for AI agents
Suggested change
|
||||||||||||||||||
| description: | ||||||||||||||||||
| "Couldn't reconnect after several attempts. Restart the daemon to recover.", | ||||||||||||||||||
| duration: Number.POSITIVE_INFINITY, | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The infinite-duration toast is never dismissed when this component unmounts (e.g., user closes the terminal pane). The toast remains visible and functional with a stale ref. Assign a stable Prompt for AI agents |
||||||||||||||||||
| action: { | ||||||||||||||||||
| label: "Restart daemon", | ||||||||||||||||||
| onClick: () => { | ||||||||||||||||||
| restartDaemonMutationRef.current.mutate(); | ||||||||||||||||||
| }, | ||||||||||||||||||
|
Comment on lines
+309
to
+311
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against concurrent mutation calls. The action button can be clicked multiple times before the mutation completes, potentially triggering concurrent daemon restarts. Add an 🛡️ Proposed fix to add pending state guard action: {
label: "Restart daemon",
onClick: () => {
+ if (!restartDaemonMutationRef.current.isPending) {
restartDaemonMutationRef.current.mutate();
+ }
},
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| }, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+303
to
+313
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because each Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
Line: 303-313
Comment:
**Multiple failing panes accumulate persistent toasts**
Because each `Terminal` instance manages its own `daemonFailureToastShownRef` and shows the toast independently, opening 3 terminal panes when the daemon is unreachable results in 3 stacked "Terminal daemon unreachable" toasts, each with its own "Restart daemon" button. Since this is a daemon-level (not pane-level) failure, a shared toast ID (e.g., a module-level constant like `DAEMON_TOAST_ID`) would allow Sonner to deduplicate updates to the same toast rather than stacking them. This mirrors how `useUpdateListener` uses `UPDATE_TOAST_ID` to prevent duplicate update toasts.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||||||||||||||||||
| } | ||||||||||||||||||
| return; | ||||||||||||||||||
|
Comment on lines
299
to
+315
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The toast is created with Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
Line: 299-315
Comment:
**Persistent toast not dismissed on component unmount**
The toast is created with `duration: Number.POSITIVE_INFINITY` but no cleanup runs when this `Terminal` instance unmounts (e.g., user closes the pane). The toast stays visible and functional indefinitely. If the user then clicks "Restart daemon," the mutation fires from an unmounted component — the daemon restarts, but `onSuccess`/`onError` can run against a detached component lifecycle. The existing `useUpdateListener` in this codebase shows the correct pattern: assign a stable `id` to the toast and call `toast.dismiss(id)` in a `useEffect` cleanup. For example, store the returned toast ID in a ref (`const toastIdRef = useRef<string | number | null>(null)`) and add a `useEffect(() => () => { if (toastIdRef.current != null) toast.dismiss(toastIdRef.current); }, [])` to guarantee cleanup on unmount.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (retryCountRef.current === 0) { | ||||||||||||||||||
| xtermRef.current?.writeln( | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The persistent "Terminal daemon unreachable" toast is never dismissed on reconnect, so a stale error can remain visible after recovery.
Prompt for AI agents