Skip to content

fix(desktop,host-service): make workspace-close terminal disposal reliable - #5752

Merged
Kitenite merged 3 commits into
mainfrom
pty-reliable-dispose
Jul 18, 2026
Merged

Kitenite merged 3 commits into
mainfrom
pty-reliable-dispose

Conversation

@Kitenite

@Kitenite Kitenite commented Jul 17, 2026 •

Copy link
Copy Markdown
Member

Summary

PR 2 of the PTY-accumulation cleanup (plans/pty-lifecycle-cleanup.md; PR 1 was #5748). Workspace close/delete disposal was fire-and-forget: the renderer broadcast swallowed every error, so a transient host-service failure silently leaked all of a workspace's sessions — and a failed kill left an active row the reaper's predicate never matched while the workspace row existed, making the session immortal.

Host-service — durable intent-to-kill:

  • New terminal_sessions.dispose_requested_at column (migration 0010, single ALTER). disposeSessionAndWait stamps it on entry (first request time wins).
  • Reaper predicate extracted as pure shouldReapRow and widened: any stamped row is retried regardless of workspace liveness. Success still deletes the row / marks it disposed, so retries converge; the reaper only acts on daemon-live sessions, so there's no perpetual no-op loop.

Renderer — await and surface:

  • disposeHostSessionsForWorkspace/ForWorktreePath now await every host and return {terminated, failed, unreachableHosts} instead of swallowing results.
  • All three call sites (useCloseWorkspace, useDeleteWorkspace, DeleteWorktreeDialog) await the result; failures surface as a toast with a Retry action. Copy reflects reality: a host-confirmed failure says the host keeps retrying (it's stamped); an unreachable host warns processes may keep running (nothing was stamped — the renderer Retry is the only recovery there).
  • The close/delete itself still proceeds — a stubborn shell never holds the workspace open.

Also: bumped the marginal 5s budget on the two-kill-sequence daemon integration test (flaky on main under load, each sequence blocks on the ~1s SIGKILL escalation).

The remaining uncovered class — renderer crashes before the broadcast ever fires — is the liveness-reaping backstop deliberately deferred in the plan.

Test Plan

  • shouldReapRow unit tests: stamped-active rows reaped; live unstamped sessions kept; dead-status/workspace-less rows still reaped
  • Full host-service suite 901/901, including the real-daemon disposal integration test 3/3 after the timeout fix
  • Repo-wide typecheck + lint clean
  • CDP verification of the close-workspace flow (incl. host-service-down failure toast) — pending, requires a dev-desktop session

Open in Stage

Summary by cubic

Makes terminal disposal reliable when closing or deleting a workspace. Prevents leaked sessions and shows a single retryable error when a kill fails, a host is unreachable, or the coordinator is down.

  • Bug Fixes

    • host-service: Added durable intent-to-kill stamp dispose_requested_at and widened the reaper via shouldReapRow to retry stamped sessions regardless of workspace liveness.
    • Desktop renderer: Disposal now awaits per-host results and combines failures into one toast with Retry. Prioritizes unreachable hosts and coordinator-unavailable cases; close/delete proceeds either way.
    • Tests: Added an end-to-end reaper retry test for dispose-stamped sessions and bumped related integration timeouts to 20s to reduce flakiness.
  • Migration

    • Adds terminal_sessions.dispose_requested_at (migration 0010_reflective_cerebro).

Written for commit 1061f5a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Workspace and worktree terminal cleanup now reports counts for successful, failed, and unreachable sessions.
    • Cleanup failures now show clearer error toasts with a Retry action.
  • Bug Fixes
    • Improved reliability when closing, deleting, or cleaning up workspaces and worktrees.
    • Prevented terminal sessions from remaining indefinitely active after an unsuccessful shutdown by persisting a retry intent and having the background cleanup reattempt disposal.

@ghost

ghost commented Jul 17, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 6 individual chapters for you:

Title
1 Add dispose_requested_at column to terminal sessions
2 Implement durable intent-to-kill in host-service
3 Widen reaper to retry stamped sessions
4 Update renderer to await and surface disposal
5 Verify disposal reliability with integration tests
6 Update PTY lifecycle cleanup plan
Open in Stage

Chapters generated by Stage for commit 1061f5a on Jul 18, 2026 8:56pm UTC.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Desktop workspace and worktree disposal now returns aggregated host outcomes, displays failures with retry actions, and awaits disposal calls. Host services persist disposal intent in terminal_sessions, allowing the reaper to retry requested sessions through a migration-backed field.

Changes

Disposal reliability

Layer / File(s) Summary
Renderer disposal results and retry flow
apps/desktop/src/renderer/lib/dispose-host-sessions.ts, apps/desktop/src/renderer/react-query/workspaces/*, apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx
Disposal calls aggregate terminated, failed, unreachable-host, and coordinator-availability outcomes; workspace and worktree deletion flows await them and surface failures through retryable toasts.
Durable disposal intent and reaper eligibility
packages/host-service/src/db/schema.ts, packages/host-service/src/terminal/terminal.ts, packages/host-service/src/terminal/reaper/*, packages/host-service/test/integration/terminal.integration.test.ts, plans/pty-lifecycle-cleanup.md
Disposal requests persist disposeRequestedAt, reaper eligibility includes pending requests, and unit and integration coverage validates the behavior.
Database migration metadata
packages/host-service/drizzle/0010_reflective_cerebro.sql, packages/host-service/drizzle/meta/*
The terminal_sessions.dispose_requested_at column and corresponding Drizzle snapshot and journal entry are recorded.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkspaceAction
  participant HostService
  participant TerminalDatabase
  participant TerminalReaper
  WorkspaceAction->>HostService: request session disposal
  HostService->>TerminalDatabase: stamp disposeRequestedAt
  HostService-->>WorkspaceAction: return disposal outcome
  TerminalReaper->>TerminalDatabase: load requested sessions
  TerminalReaper->>HostService: reap stamped session
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main reliability fix.
Description check ✅ Passed The description covers the change and testing well, but it omits the template's checklist section.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pty-reliable-dispose

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.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes the two main terminal-session leak vectors on workspace close/delete: the renderer previously fire-and-forgot disposal broadcasts (swallowing all errors), and a failed kill left an active DB row that the reaper's old predicate never retried once the workspace row was gone.

  • Host-service: adds a nullable dispose_requested_at column (migration 0010); disposeSessionAndWait stamps it on entry with a first-write-wins isNull guard, and the extracted shouldReapRow predicate is widened to retry any stamped active row regardless of workspace liveness.
  • Renderer: disposeHostSessionsForWorkspace/ForWorktreePath now await all host calls and return a structured {terminated, failed, unreachableHosts} result; all three call sites (useCloseWorkspace, useDeleteWorkspace, DeleteWorktreeDialog) surface failures as a toast with a Retry action that loops back through toastDisposeFailures.
  • Integration test timeout bumped from 5 s to 20 s to accommodate the ~1 s SIGKILL-escalation latency per kill sequence under load.

Confidence Score: 4/5

Safe to merge; the core durability logic (stamp + reaper retry + renderer await + toast) is sound, and the test coverage for shouldReapRow and the integration test are solid.

The one finding is in toastDisposeFailures: when a multi-host run produces both unreachableHosts > 0 and failed > 0, the early return shows only the unreachable message and drops context about stamped-but-failed sessions that the reaper is already retrying. This is a UX information-loss issue in a multi-org scenario rather than a data-loss or crash path, but the toast copy becomes misleading in that edge case.

apps/desktop/src/renderer/lib/dispose-host-sessions.ts — the combined-error branch in toastDisposeFailures.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/lib/dispose-host-sessions.ts Refactored from fire-and-forget to awaited disposal with structured result and retry toast; minor UX issue when both unreachableHosts and failed are non-zero simultaneously.
apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts Awaits dispose result and surfaces failures via toastDisposeFailures; retryDispose closure correctly captures variables.id.
apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts Mirrors useCloseWorkspace change; awaits dispose and surfaces failures. Correct.
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx Uses void+.then() pattern (fire-and-toast) for the dispose rather than await; intentional since onSuccess isn't awaiting dispose-completion before dialog teardown, and no subsequent ops depend on it.
packages/host-service/src/terminal/reaper/reaper.ts Extracts shouldReapRow predicate, adds disposeRequestedAt to TerminalRow, widens reap condition to retry stamped rows; logic is correct and the predicate is unit-tested.
packages/host-service/src/terminal/terminal.ts disposeSessionAndWait stamps disposeRequestedAt on entry using an isNull guard (first-request-wins); synchronous SQLite .run() call is appropriate for better-sqlite3.
packages/host-service/drizzle/0010_reflective_cerebro.sql Single nullable integer column added with ALTER TABLE; backward-compatible, no data migration needed.
packages/host-service/src/terminal/reaper/reaper.test.ts New shouldReapRow unit tests cover stamped-active, live-unstamped, dead-status, and workspace-less cases correctly.
packages/host-service/test/integration/terminal.integration.test.ts Timeout bumped from 5s to 20s for the two-kill-sequence test; justified by comment explaining the ~1s SIGKILL escalation latency per sequence.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Workspace close / delete / worktree delete] --> B[disposeHostSessionsForWorkspace\nor ForWorktreePath]
    B --> C[localHostClients: one per org]
    C --> D{For each host}
    D -- success --> E[terminated++]
    D -- kill failed --> F[failed++\nstamped disposeRequestedAt]
    D -- host unreachable --> G[unreachableHosts++]
    E & F & G --> H[DisposeHostSessionsResult]
    H --> I{toastDisposeFailures}
    I -- all zero --> J[no-op]
    I -- unreachableHosts > 0 --> K[toast: processes may keep running\nRetry action]
    I -- failed > 0 only --> L[toast: N sessions failed\nhost will keep retrying]
    K & L --> M{User clicks Retry?}
    M -- yes --> B
    F --> N[Reaper next pass\nup to 5 min]
    N --> O{shouldReapRow?\ndisposeRequestedAt != null}
    O -- yes --> P[disposeSessionAndWait again]
    P -- success --> Q[status = disposed]
    P -- fail --> N
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Workspace close / delete / worktree delete] --> B[disposeHostSessionsForWorkspace\nor ForWorktreePath]
    B --> C[localHostClients: one per org]
    C --> D{For each host}
    D -- success --> E[terminated++]
    D -- kill failed --> F[failed++\nstamped disposeRequestedAt]
    D -- host unreachable --> G[unreachableHosts++]
    E & F & G --> H[DisposeHostSessionsResult]
    H --> I{toastDisposeFailures}
    I -- all zero --> J[no-op]
    I -- unreachableHosts > 0 --> K[toast: processes may keep running\nRetry action]
    I -- failed > 0 only --> L[toast: N sessions failed\nhost will keep retrying]
    K & L --> M{User clicks Retry?}
    M -- yes --> B
    F --> N[Reaper next pass\nup to 5 min]
    N --> O{shouldReapRow?\ndisposeRequestedAt != null}
    O -- yes --> P[disposeSessionAndWait again]
    P -- success --> Q[status = disposed]
    P -- fail --> N
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src/renderer/lib/dispose-host-sessions.ts:119-132
When both `unreachableHosts > 0` and `failed > 0` are true simultaneously (e.g., two orgs running host-services, one unreachable and one with failed kills), the early `return` after the unreachable toast discards the `failed` count entirely. The user sees "processes may keep running" for everything, missing the fact that the stamped-but-failed sessions on the reachable host are already queued for reaper retry. At minimum, the description could mention both conditions, or the `failed` branch should be allowed to fire as a second toast.

```suggestion
	if (result.unreachableHosts > 0) {
		const description =
			result.failed > 0
				? "Its terminal processes may keep running. The reachable host will keep retrying failed sessions."
				: "Its terminal processes may keep running.";
		toast.error("Couldn't reach the host to close terminal sessions", {
			description,
			action: retryAction,
		});
		return;
	}
	toast.error(
		`Failed to close ${result.failed} terminal session${result.failed === 1 ? "" : "s"}`,
		{
			description: "The host will keep retrying in the background.",
			action: retryAction,
		},
	);
```

Reviews (1): Last reviewed commit: "fix(desktop,host-service): make workspac..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

Comment on lines +119 to +132
if (result.unreachableHosts > 0) {
toast.error("Couldn't reach the host to close terminal sessions", {
description: "Its terminal processes may keep running.",
action: retryAction,
});
return;
}
toast.error(
`Failed to close ${result.failed} terminal session${result.failed === 1 ? "" : "s"}`,
{
description: "The host will keep retrying in the background.",
action: retryAction,
},
);

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.

P2 When both unreachableHosts > 0 and failed > 0 are true simultaneously (e.g., two orgs running host-services, one unreachable and one with failed kills), the early return after the unreachable toast discards the failed count entirely. The user sees "processes may keep running" for everything, missing the fact that the stamped-but-failed sessions on the reachable host are already queued for reaper retry. At minimum, the description could mention both conditions, or the failed branch should be allowed to fire as a second toast.

Suggested change
if (result.unreachableHosts > 0) {
toast.error("Couldn't reach the host to close terminal sessions", {
description: "Its terminal processes may keep running.",
action: retryAction,
});
return;
}
toast.error(
`Failed to close ${result.failed} terminal session${result.failed === 1 ? "" : "s"}`,
{
description: "The host will keep retrying in the background.",
action: retryAction,
},
);
if (result.unreachableHosts > 0) {
const description =
result.failed > 0
? "Its terminal processes may keep running. The reachable host will keep retrying failed sessions."
: "Its terminal processes may keep running.";
toast.error("Couldn't reach the host to close terminal sessions", {
description,
action: retryAction,
});
return;
}
toast.error(
`Failed to close ${result.failed} terminal session${result.failed === 1 ? "" : "s"}`,
{
description: "The host will keep retrying in the background.",
action: retryAction,
},
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/lib/dispose-host-sessions.ts
Line: 119-132

Comment:
When both `unreachableHosts > 0` and `failed > 0` are true simultaneously (e.g., two orgs running host-services, one unreachable and one with failed kills), the early `return` after the unreachable toast discards the `failed` count entirely. The user sees "processes may keep running" for everything, missing the fact that the stamped-but-failed sessions on the reachable host are already queued for reaper retry. At minimum, the description could mention both conditions, or the `failed` branch should be allowed to fire as a second toast.

```suggestion
	if (result.unreachableHosts > 0) {
		const description =
			result.failed > 0
				? "Its terminal processes may keep running. The reachable host will keep retrying failed sessions."
				: "Its terminal processes may keep running.";
		toast.error("Couldn't reach the host to close terminal sessions", {
			description,
			action: retryAction,
		});
		return;
	}
	toast.error(
		`Failed to close ${result.failed} terminal session${result.failed === 1 ? "" : "s"}`,
		{
			description: "The host will keep retrying in the background.",
			action: retryAction,
		},
	);
```

How can I resolve this? If you propose a fix, please make it concise.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

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.

All reported issues were addressed across 13 files

You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Architecture diagram
sequenceDiagram
    participant Renderer as Renderer (React)
    participant Dispose as dispose-host-sessions
    participant WorkspaceAPI as Workspace API (tRPC)
    participant HostService as Host Service (per org)
    participant DB as SQLite (terminal_sessions)
    participant Reaper as Background Reaper
    participant Daemon as PTY Daemon

    Note over Renderer,Daemon: Workspace Close/Delete Flow

    Renderer->>WorkspaceAPI: closeWorkspace(id) or deleteWorkspace(id)
    WorkspaceAPI-->>Renderer: success
    Renderer->>Dispose: disposeHostSessionsForWorkspace(id)

    Dispose->>Dispose: getLocalHostClients()
    loop For each host service
        Dispose->>HostService: disposeWorkspaceSessions({ workspaceId })
        alt Host reachable
            HostService->>DB: UPDATE terminal_sessions SET dispose_requestedAt = now
            HostService->>Daemon: kill sessions (SIGTERM → SIGKILL)
            alt Kill succeeds
                HostService->>DB: DELETE or mark sessions disposed
                HostService-->>Dispose: { terminated: N, failed: 0 }
            else Kill fails (daemon hiccup)
                HostService-->>Dispose: { terminated: 0, failed: N }
                Note over DB,Reaper: Row remains active with dispose_requestedAt stamp
            end
        else Host unreachable
            HostService-->>Dispose: null
            Note over Dispose: No stamp written — sessions unreachable
        end
    end
    Dispose-->>Renderer: { terminated, failed, unreachableHosts }

    alt Any failures
        Renderer->>Renderer: toastDisposeFailures()
        Renderer->>Renderer: Show toast with Retry button
        opt User clicks Retry
            Renderer->>Dispose: disposeHostSessionsForWorkspace(id)
        end
    else All clean
        Note over Renderer: No toast
    end

    Note over DB,Reaper: Background Recovery (independent loop)

    loop Every REAP_INTERVAL_MS
        Reaper->>DB: SELECT sessions with shouldReapRow()
        alt disposeRequestedAt != null (stamped, active)
            Reaper->>Daemon: kill session
            Reaper->>DB: DELETE or mark disposed
        else Dead status or no workspace
            Reaper->>Daemon: cleanup
        else Live, unstamped, has workspace
            Note over Reaper: Skip — still active
        end
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@coderabbitai coderabbitai Bot left a comment

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.

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 `@packages/host-service/test/integration/terminal.integration.test.ts`:
- Around line 323-324: Restore SUPERSET_PTY_DAEMON_SOCKET and SUPERSET_HOME_DIR
to their original values during the integration test cleanup, rather than
leaving the temporary socket and home directory assignments in place. Update the
setup/teardown flow around these environment variables so subsequent tests
cannot inherit stale paths, while preserving the existing cleanup behavior.
🪄 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

Run ID: dffba2a3-9e3f-419d-a465-07da6dd9813d

📥 Commits

Reviewing files that changed from the base of the PR and between f6dac54 and bf5064d.

📒 Files selected for processing (1)
  • packages/host-service/test/integration/terminal.integration.test.ts

Comment on lines +323 to +324
process.env.SUPERSET_PTY_DAEMON_SOCKET = socketPath;
process.env.SUPERSET_HOME_DIR = tmp;

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore the process environment during cleanup.

Lines 323-324 leave SUPERSET_PTY_DAEMON_SOCKET and SUPERSET_HOME_DIR pointing at a directory removed on Line 385. Subsequent tests can therefore connect through stale paths and become order-dependent.

Proposed fix
+		const previousSocket = process.env.SUPERSET_PTY_DAEMON_SOCKET;
+		const previousHome = process.env.SUPERSET_HOME_DIR;
 		try {
 			// ...
 		} finally {
 			stopReaper?.();
 			await scenario.host.trpc.terminal.killSession
 				.mutate({ workspaceId: scenario.workspaceId, terminalId: sparedId })
 				.catch(() => {});
 			await disposeDaemonClient();
 			await stopDaemonProcess(daemonProcess);
 			rmSync(tmp, { recursive: true, force: true });
+			if (previousSocket === undefined) {
+				delete process.env.SUPERSET_PTY_DAEMON_SOCKET;
+			} else {
+				process.env.SUPERSET_PTY_DAEMON_SOCKET = previousSocket;
+			}
+			if (previousHome === undefined) {
+				delete process.env.SUPERSET_HOME_DIR;
+			} else {
+				process.env.SUPERSET_HOME_DIR = previousHome;
+			}
 		}
📝 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
process.env.SUPERSET_PTY_DAEMON_SOCKET = socketPath;
process.env.SUPERSET_HOME_DIR = tmp;
const previousSocket = process.env.SUPERSET_PTY_DAEMON_SOCKET;
const previousHome = process.env.SUPERSET_HOME_DIR;
try {
process.env.SUPERSET_PTY_DAEMON_SOCKET = socketPath;
process.env.SUPERSET_HOME_DIR = tmp;
// ...
} finally {
stopReaper?.();
await scenario.host.trpc.terminal.killSession
.mutate({ workspaceId: scenario.workspaceId, terminalId: sparedId })
.catch(() => {});
await disposeDaemonClient();
await stopDaemonProcess(daemonProcess);
rmSync(tmp, { recursive: true, force: true });
if (previousSocket === undefined) {
delete process.env.SUPERSET_PTY_DAEMON_SOCKET;
} else {
process.env.SUPERSET_PTY_DAEMON_SOCKET = previousSocket;
}
if (previousHome === undefined) {
delete process.env.SUPERSET_HOME_DIR;
} else {
process.env.SUPERSET_HOME_DIR = previousHome;
}
}
🤖 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 `@packages/host-service/test/integration/terminal.integration.test.ts` around
lines 323 - 324, Restore SUPERSET_PTY_DAEMON_SOCKET and SUPERSET_HOME_DIR to
their original values during the integration test cleanup, rather than leaving
the temporary socket and home directory assignments in place. Update the
setup/teardown flow around these environment variables so subsequent tests
cannot inherit stale paths, while preserving the existing cleanup behavior.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 1 file (changes from recent commits).

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/host-service/test/integration/terminal.integration.test.ts">

<violation number="1" location="packages/host-service/test/integration/terminal.integration.test.ts:379">
P3: The test cleans up `sparedId` via killSession but never cleans up the DB-stamped `stampedId`. After the reaper kills the daemon session, the row still exists in the DB (stamped, possibly still `active` if reaper's `disposeSessionAndWait` succeeded). Since the test uses `scenario.host.db` which is an in-memory test DB cleaned up via `host.dispose()`, this is safe in practice — but the asymmetry with `sparedId` (which gets an explicit killSession) is inconsistent. Consider deleting the stamped row after the reaper assertion or adding a brief comment explaining why it's unnecessary.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

);
expect((await aliveIds()).has(sparedId)).toBe(true);
} finally {
stopReaper?.();

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.

P3: The test cleans up sparedId via killSession but never cleans up the DB-stamped stampedId. After the reaper kills the daemon session, the row still exists in the DB (stamped, possibly still active if reaper's disposeSessionAndWait succeeded). Since the test uses scenario.host.db which is an in-memory test DB cleaned up via host.dispose(), this is safe in practice — but the asymmetry with sparedId (which gets an explicit killSession) is inconsistent. Consider deleting the stamped row after the reaper assertion or adding a brief comment explaining why it's unnecessary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/test/integration/terminal.integration.test.ts, line 379:

<comment>The test cleans up `sparedId` via killSession but never cleans up the DB-stamped `stampedId`. After the reaper kills the daemon session, the row still exists in the DB (stamped, possibly still `active` if reaper's `disposeSessionAndWait` succeeded). Since the test uses `scenario.host.db` which is an in-memory test DB cleaned up via `host.dispose()`, this is safe in practice — but the asymmetry with `sparedId` (which gets an explicit killSession) is inconsistent. Consider deleting the stamped row after the reaper assertion or adding a brief comment explaining why it's unnecessary.</comment>

<file context>
@@ -296,6 +299,93 @@ describe("terminal router integration", () => {
+			);
+			expect((await aliveIds()).has(sparedId)).toBe(true);
+		} finally {
+			stopReaper?.();
+			await scenario.host.trpc.terminal.killSession
+				.mutate({ workspaceId: scenario.workspaceId, terminalId: sparedId })
</file context>

@Kitenite

Copy link
Copy Markdown
Member Author

Addressed the substantive review findings in 1061f5a:

  • Coordinator lookup failure masqueraded as success (cubic P1): localHostClients caught the getConnections failure to [], so a failed enumeration looked like "no hosts" → all-zero result → no toast. Now returns null on lookup failure vs [] for genuinely-no-hosts; new coordinatorUnavailable flag surfaces a Retry toast (nothing was stamped, so renderer Retry is the only recovery).
  • Combined unreachable + failed only showed one message (greptile P2, cubic P2): the unreachable branch early-returned and dropped the failed count. Now the unreachable toast appends "N more will be retried in the background" when confirmed failures also exist.
  • Rejected retry → unhandled rejection (cubic P2): retry's .then now has a .catch.

Not changed: test env-var restore (coderabbit) — afterEach already deletes SUPERSET_PTY_DAEMON_SOCKET/SUPERSET_HOME_DIR suite-wide; my test matches the two existing tests in the file.

Re-verified: host-service suite 902/902; typecheck + lint clean; CDP re-verification of the two new/changed toast branches against the running app — coordinator-unavailable and combined unreachable+failed both render correct copy with a working Retry, success still adds no toast.

This branch was previously deployed

1 inactive deployment
preview — 1061f5af Deployed Jul 18, 2026 by Kitenite via Deploy API #6631
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