Skip to content

Fix performance issues and remove non-persisting terminal#1105

Merged
Kitenite merged 11 commits into
mainfrom
perf-issues-1
Feb 1, 2026
Merged

Fix performance issues and remove non-persisting terminal#1105
Kitenite merged 11 commits into
mainfrom
perf-issues-1

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 1, 2026

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • New Features

    • Terminal exit events now include an exit reason (killed, exited, or error).
  • Changes

    • Terminal persistence setting and related UI removed from Settings and search.
    • Terminal management now consistently uses daemon-backed sessions.
    • Killed sessions are tracked to avoid unintended restarts; session kill/clear actions updated.
    • Tray/Restart Daemon menu simplified (restart entry always enabled).

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

Consolidates terminal handling to a daemon-backed model: removes in-process TerminalManager and persistence settings, adds killed-session tombstones and exit reasons ("killed" | "exited" | "error"), updates router and renderer APIs to propagate reason, and simplifies daemon-related public APIs.

Changes

Cohort / File(s) Summary
Terminal Exit Event Enhancement
apps/desktop/src/lib/trpc/routers/notifications.ts, apps/desktop/src/main/lib/terminal/types.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts, apps/desktop/src/main/windows/main.ts
Added optional `reason?: "killed"
Daemon Manager Kill Tracking
apps/desktop/src/main/lib/terminal/daemon/daemon-manager.ts, apps/desktop/src/main/lib/terminal/daemon/daemon-manager.test.ts, apps/desktop/src/main/lib/terminal/daemon/types.ts, apps/desktop/src/main/lib/terminal/daemon/constants.ts
Implemented killed-session tombstones with MAX_KILLED_SESSION_TOMBSTONES (1000); record/evict tombstones, mark exitReason="killed", and added tests for kill-tracking and exit labeling.
Terminal Error Handling & Types
apps/desktop/src/main/lib/terminal/errors.ts, apps/desktop/src/main/lib/terminal/types.ts
Added TerminalKilledError and TERMINAL_SESSION_KILLED_MESSAGE; extended types with allowKilled on create, and exitReason/killedByUserAt on sessions/events.
Terminal Router & Stream API Changes
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts, apps/desktop/src/lib/trpc/routers/terminal/terminal.stream.test.ts
Removed daemonModeEnabled from several router responses; added allowKilled input to createOrAttach; stream/subscription exit payloads include reason; tests and mocks refactored for daemon-backed flows.
Terminal Manager Removal & Module Consolidation
apps/desktop/src/main/lib/terminal/manager.ts, apps/desktop/src/main/lib/terminal/manager.test.ts, apps/desktop/src/main/lib/terminal/index.ts, apps/desktop/src/main/index.ts
Deleted TerminalManager implementation and its test suite; removed related exports and orphaned-daemon shutdown call; simplified tryListExistingDaemonSessions to return only sessions.
Terminal Persistence Settings Removal
apps/desktop/src/lib/trpc/routers/settings/index.ts, apps/desktop/src/shared/constants.ts, apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
Removed DEFAULT_TERMINAL_PERSISTENCE constant, get/set terminal persistence endpoints, and terminal persistence settings entry from settings search.
Runtime & Management Capability Updates
apps/desktop/src/main/lib/workspace-runtime/local.ts, apps/desktop/src/main/lib/workspace-runtime/types.ts, apps/desktop/src/main/lib/tray/index.ts
Local runtime now always uses DaemonTerminalManager; TerminalRuntime.management is non-null; capabilities hardcoded persistent/coldRestore=true; tray submenu no longer takes daemonRunning.
Renderer Terminal UI Updates
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, .../hooks/useTerminalStream.ts, .../hooks/useTerminalRestore.ts, .../hooks/useTerminalColdRestore.ts
Propagated exit reason through restore/stream hooks; updated handler signatures to accept optional reason; replaced in-memory kill-tracking checks with reason-based logic.
Terminal Settings UI Removal
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx
Removed terminal persistence UI, queries, and mutations; simplified sessions UI and removed persistence-conditioned rendering.
Kill Tracking Utility Removal
apps/desktop/src/renderer/lib/terminal-kill-tracking.ts, apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts
Deleted killed-by-user in-memory tracking and its helpers; removed usages and replaced logic to rely on reason-based flows and store-based pane destruction checks.
Pane Guards & Lifecycle Adjustments
apps/desktop/src/renderer/screens/.../Terminal/pane-guards.ts, .../pane-guards.test.ts, .../hooks/useTerminalLifecycle.ts
Added isPaneDestroyed utility and tests; lifecycle now checks store destruction to decide immediate kill vs. delayed detach.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Renderer as Renderer (UI)
participant TRPC as TRPC Router
participant Daemon as DaemonTerminalManager
participant Main as Main Process
Renderer->>TRPC: request kill/list/createOrAttach (may include allowKilled)
TRPC->>Daemon: listSessions / kill / createOrAttach
Daemon-->>TRPC: sessions, killedCount, or TerminalKilledError
TRPC-->>Renderer: response (sessions, killedCount) / stream exit events with reason
Daemon->>Main: emit terminalExit event (exitCode + reason)
Main-->>Renderer: forward TERMINAL_EXIT notification (includes reason)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

"🐰 Hopped through code with whiskers bright,
Tombstones set and exits logged just right.
Persistence gone, the daemon leads the way,
Kill reasons travel home by end of day.
Tiny carrots of joy—keep tests in sight!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the template placeholders with no actual content, implementation details, testing information, or issue links provided. Fill in all required sections: provide a clear description of changes, link related issues, specify the type of change, describe testing performed, and add any relevant context.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Fix performance issues and remove non-persisting terminal' is related to the changeset but lacks specificity about the actual changes made.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf-issues-1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts`:
- Around line 293-297: The Promise.allSettled call that runs terminal.kill for
each paneId is silently ignoring failures; change the code around the
Promise.allSettled calls (where beforeIds.map(...) is passed to
Promise.allSettled and the similar block at the other occurrence) to await the
settled results, iterate over them, and for any result.status === "rejected"
call the module's logger (or processLogger) to log an error with context
including the paneId and the rejection reason so failed kills are not swallowed.
Ensure you reference the terminal.kill call and include paneId in the log
message.

In `@apps/desktop/src/main/lib/terminal/index.ts`:
- Around line 33-47: The catch block in tryListExistingDaemonSessions currently
only logs errors when DEBUG_TERMINAL is set, which swallows failures in
production; update the catch to always emit a warning-level log with context and
the error (e.g., using console.warn or the module's logger) and retain the debug
log when DEBUG_TERMINAL is true; reference getTerminalHostClient and
client.listSessions so the message clearly states "Failed to list existing
daemon sessions" and includes the error details.
🧹 Nitpick comments (5)
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx (1)

744-750: Extract the session-count threshold into a named constant.
Avoid a hard-coded 20 in UI logic.

♻️ Suggested refactor
+const SESSION_WARNING_THRESHOLD = 20;
...
- {aliveSessions.length >= 20 && (
+ {aliveSessions.length >= SESSION_WARNING_THRESHOLD && (
As per coding guidelines: Avoid magic numbers by extracting them to named constants at module top.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts (1)

6-7: Consider extracting TerminalExitReason to a shared types location.

This type is duplicated identically in apps/desktop/src/main/lib/terminal/types.ts (line 28). While the duplication is small, it creates a maintenance burden and potential for drift if one definition changes without the other.

Consider moving this to a shared types file that both renderer and main process can import, or re-exporting from a common location.

apps/desktop/src/main/lib/terminal/daemon/daemon-manager.test.ts (1)

62-65: Consider adding a test for the MAX_KILLED_SESSION_TOMBSTONES limit.

The tombstone eviction logic (removing oldest when exceeding 1000 entries) is not covered by tests. A test verifying that the oldest tombstone is evicted when the limit is reached would improve confidence in the bounded memory behavior.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

135-137: Prefer a params object for the exit handler signature.

With three positional args, consider { exitCode, xterm, reason } and update call sites accordingly to reduce positional confusion.

As per coding guidelines: Functions with 2+ parameters should accept a single params object with named properties instead of positional arguments.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts (1)

22-26: Prefer a params object for handleTerminalExit.

Now that the handler takes three positional args, consider a single { exitCode, xterm, reason } object and update call sites to match.

As per coding guidelines: Functions with 2+ parameters should accept a single params object with named properties instead of positional arguments.

Also applies to: 58-66

Comment thread apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Comment thread apps/desktop/src/main/lib/terminal/index.ts
@Kitenite Kitenite merged commit 464eaa6 into main Feb 1, 2026
4 of 5 checks passed
@Kitenite Kitenite deleted the perf-issues-1 branch February 1, 2026 03:18
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 1, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts`:
- Around line 350-375: The returned killedCount currently uses toKill.length
(attempts) rather than counting successful kills; after awaiting
Promise.allSettled on paneIds.map(paneId => terminal.kill({ paneId })), compute
the number of results with status === "fulfilled" (or filter results by
fulfilled) and return that value as killedCount; keep the existing logging for
rejected results but change the final return to use the computed successfulCount
(referencing terminal.management.listSessions, terminal.kill, paneIds, results,
and killedCount).

In `@apps/desktop/src/main/lib/terminal/daemon/daemon-manager.test.ts`:
- Around line 74-83: The session fixture used in daemon-manager.test.ts is
missing the sessionId property and must mirror the real invariant (sessionId ===
paneId); update the object passed to sessions.set (the entry created with paneId
and workspaceId) to include sessionId set to paneId so tests reflect the actual
session shape used by the daemon code (look for sessions.set and paneId in the
test).

In `@apps/desktop/src/main/lib/workspace-runtime/local.ts`:
- Around line 29-35: Update the adapter doc comment to reflect that management
is always provided (remove or change the conditional "only when available
(daemon mode)" wording); edit the comment that describes Adapts
DaemonTerminalManager to the TerminalRuntime interface (the adapter for
DaemonTerminalManager/TerminalRuntime) so bullet 2 states that management is
exposed (always) rather than conditionally, ensuring the three-bullet list
matches current behavior.
🧹 Nitpick comments (5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/pane-guards.ts (1)

3-6: Use a params object for isPaneDestroyed to meet the 2+ args guideline.

This keeps call sites self-documenting and aligns with the project rule.

♻️ Suggested refactor
-export const isPaneDestroyed = (
-	panes: Record<string, Pane> | undefined,
-	paneId: string,
-): boolean => !panes?.[paneId];
+export const isPaneDestroyed = ({
+	panes,
+	paneId,
+}: {
+	panes?: Record<string, Pane>;
+	paneId: string;
+}): boolean => !panes?.[paneId];
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalRestore.ts (1)

22-26: Prefer a params object for onExitEvent (now 3 args).

This aligns with the 2+ args guideline and makes call sites clearer as the signature grows.

♻️ Suggested refactor
-	onExitEvent: (
-		exitCode: number,
-		xterm: XTerm,
-		reason?: TerminalExitReason,
-	) => void;
+	onExitEvent: (params: {
+		exitCode: number;
+		xterm: XTerm;
+		reason?: TerminalExitReason;
+	}) => void;
-				onExitEventRef.current(event.exitCode, xterm, event.reason);
+				onExitEventRef.current({
+					exitCode: event.exitCode,
+					xterm,
+					reason: event.reason,
+				});

Also applies to: 100-100

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts (1)

571-576: Extract the 50ms detach delay to a named constant.

Magic numbers reduce readability and are harder to tune consistently.

♻️ Suggested refactor
+const DETACH_TIMEOUT_MS = 50;
-			const detachTimeout = setTimeout(() => {
+			const detachTimeout = setTimeout(() => {
 				detachRef.current({ paneId });
 				pendingDetaches.delete(paneId);
 				coldRestoreState.delete(paneId);
-			}, 50);
+			}, DETACH_TIMEOUT_MS);
apps/desktop/src/main/lib/terminal/daemon/daemon-manager.test.ts (1)

93-95: Extract exit code/signal literals into named constants.
This makes intent explicit (e.g., SIGTERM vs. generic numbers) and keeps tests consistent.

As per coding guidelines, avoid magic numbers by extracting them to named constants at module top.

apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/TerminalSettings.tsx (1)

739-746: Extract the session warning threshold into a named constant.
This avoids a hard-coded value in the UI logic.

♻️ Suggested refactor
+const SESSION_WARNING_THRESHOLD = 20;
-							{aliveSessions.length >= 20 && (
+							{aliveSessions.length >= SESSION_WARNING_THRESHOLD && (

As per coding guidelines: Avoid magic numbers by extracting them to named constants at module top.

Comment on lines +350 to +375
const { sessions } = await terminal.management.listSessions();
const toKill = sessions.filter(
(session) => session.workspaceId === input.workspaceId,
);

for (const session of toKill) {
userKilledSessions.add(session.sessionId);
await client.kill({ sessionId: session.sessionId });
if (toKill.length > 0) {
const paneIds = toKill.map((session) => session.sessionId);
const results = await Promise.allSettled(
paneIds.map((paneId) => terminal.kill({ paneId })),
);
for (const [index, result] of results.entries()) {
if (result.status === "rejected") {
const paneId = paneIds[index];
logger.error(
`[killDaemonSessionsForWorkspace] terminal.kill failed for paneId=${paneId}`,
{
paneId,
workspaceId: input.workspaceId,
reason: result.reason,
},
);
}
}
}

return { daemonModeEnabled: true, killedCount: toKill.length };
return { killedCount: toKill.length };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

killedCount should reflect successful kills, not attempts.
Right now it returns toKill.length even if some kills fail, which can overstate what actually happened.

🛠️ Proposed fix
 			const toKill = sessions.filter(
 				(session) => session.workspaceId === input.workspaceId,
 			);
 
-			if (toKill.length > 0) {
-				const paneIds = toKill.map((session) => session.sessionId);
-				const results = await Promise.allSettled(
-					paneIds.map((paneId) => terminal.kill({ paneId })),
-				);
-				for (const [index, result] of results.entries()) {
-					if (result.status === "rejected") {
-						const paneId = paneIds[index];
-						logger.error(
-							`[killDaemonSessionsForWorkspace] terminal.kill failed for paneId=${paneId}`,
-							{
-								paneId,
-								workspaceId: input.workspaceId,
-								reason: result.reason,
-							},
-						);
-					}
-				}
-			}
-
-			return { killedCount: toKill.length };
+			let killedCount = 0;
+			if (toKill.length > 0) {
+				const paneIds = toKill.map((session) => session.sessionId);
+				const results = await Promise.allSettled(
+					paneIds.map((paneId) => terminal.kill({ paneId })),
+				);
+				for (const [index, result] of results.entries()) {
+					if (result.status === "rejected") {
+						const paneId = paneIds[index];
+						logger.error(
+							`[killDaemonSessionsForWorkspace] terminal.kill failed for paneId=${paneId}`,
+							{
+								paneId,
+								workspaceId: input.workspaceId,
+								reason: result.reason,
+							},
+						);
+					}
+				}
+				killedCount = results.filter(
+					(result) => result.status === "fulfilled",
+				).length;
+			}
+
+			return { killedCount };
🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts` around lines 350 -
375, The returned killedCount currently uses toKill.length (attempts) rather
than counting successful kills; after awaiting Promise.allSettled on
paneIds.map(paneId => terminal.kill({ paneId })), compute the number of results
with status === "fulfilled" (or filter results by fulfilled) and return that
value as killedCount; keep the existing logging for rejected results but change
the final return to use the computed successfulCount (referencing
terminal.management.listSessions, terminal.kill, paneIds, results, and
killedCount).

Comment on lines +74 to +83
sessions.set(paneId, {
paneId,
workspaceId: "ws-1",
isAlive: true,
lastActive: Date.now(),
cwd: "",
pid: 123,
cols: 80,
rows: 24,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add sessionId to the session fixture to preserve the daemon invariant.
Without it, the test diverges from real session shape and can mask issues that read sessionId directly.

✅ Suggested fix
 sessions.set(paneId, {
   paneId,
+  sessionId: paneId,
   workspaceId: "ws-1",
   isAlive: true,
   lastActive: Date.now(),
   cwd: "",
   pid: 123,
   cols: 80,
   rows: 24,
 });

Based on learnings: In the terminal daemon code under apps/desktop/src/main/lib/terminal/daemon, sessionId is intentionally set equal to paneId, so tests should reflect that invariant.

📝 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.

Suggested change
sessions.set(paneId, {
paneId,
workspaceId: "ws-1",
isAlive: true,
lastActive: Date.now(),
cwd: "",
pid: 123,
cols: 80,
rows: 24,
});
sessions.set(paneId, {
paneId,
sessionId: paneId,
workspaceId: "ws-1",
isAlive: true,
lastActive: Date.now(),
cwd: "",
pid: 123,
cols: 80,
rows: 24,
});
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/terminal/daemon/daemon-manager.test.ts` around
lines 74 - 83, The session fixture used in daemon-manager.test.ts is missing the
sessionId property and must mirror the real invariant (sessionId === paneId);
update the object passed to sessions.set (the entry created with paneId and
workspaceId) to include sessionId set to paneId so tests reflect the actual
session shape used by the daemon code (look for sessions.set and paneId in the
test).

Comment on lines +29 to 35
* Adapts DaemonTerminalManager to the TerminalRuntime interface.
*
* This adapter:
* 1. Wraps the underlying manager with the common interface
* 2. Exposes management capabilities only when available (daemon mode)
* 3. Provides capability flags for UI feature detection
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update adapter doc: management is no longer conditional.
The comment says management is exposed “only when available (daemon mode)”, but this adapter now always provides management. Please update the bullet to match current behavior.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/workspace-runtime/local.ts` around lines 29 - 35,
Update the adapter doc comment to reflect that management is always provided
(remove or change the conditional "only when available (daemon mode)" wording);
edit the comment that describes Adapts DaemonTerminalManager to the
TerminalRuntime interface (the adapter for
DaemonTerminalManager/TerminalRuntime) so bullet 2 states that management is
exposed (always) rather than conditionally, ensuring the three-bullet list
matches current behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant