diff --git a/frontend/src/renderer/components/ShellTopbar.test.tsx b/frontend/src/renderer/components/ShellTopbar.test.tsx
index 3b2c9d4190..27b9733aef 100644
--- a/frontend/src/renderer/components/ShellTopbar.test.tsx
+++ b/frontend/src/renderer/components/ShellTopbar.test.tsx
@@ -5,7 +5,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { WorkspaceSession } from "../types/workspace";
import { TopbarKillButton } from "./ShellTopbar";
-const { postMock } = vi.hoisted(() => ({
+const { onKilledMock, postMock } = vi.hoisted(() => ({
+ onKilledMock: vi.fn(),
postMock: vi.fn(),
}));
@@ -35,7 +36,20 @@ const worker: WorkspaceSession = {
prs: [],
};
-function renderKill(session: WorkspaceSession = worker) {
+const orchestrator: WorkspaceSession = {
+ id: "orch-1",
+ workspaceId: "proj-1",
+ workspaceName: "my-app",
+ title: "orchestrator",
+ provider: "claude-code",
+ kind: "orchestrator",
+ branch: "main",
+ status: "working",
+ updatedAt: "2026-06-10T00:00:00Z",
+ prs: [],
+};
+
+function renderKill(session: WorkspaceSession = worker, orchestratorId?: string) {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
@@ -44,13 +58,14 @@ function renderKill(session: WorkspaceSession = worker) {
});
render(
-
+
,
);
return queryClient;
}
beforeEach(() => {
+ onKilledMock.mockReset();
postMock.mockReset();
postMock.mockResolvedValue({ data: { ok: true, sessionId: "sess-1" }, error: undefined });
});
@@ -89,4 +104,26 @@ describe("TopbarKillButton", () => {
expect(await screen.findByText("session not found")).toBeInTheDocument();
});
+
+ it("navigates back to the project orchestrator after a successful kill", async () => {
+ renderKill(worker, orchestrator.id);
+
+ await userEvent.click(screen.getByRole("button", { name: "Kill session" }));
+ await userEvent.click(screen.getByRole("button", { name: "Confirm kill" }));
+
+ await waitFor(() => {
+ expect(onKilledMock).toHaveBeenCalledWith("proj-1", "orch-1");
+ });
+ });
+
+ it("falls back to the project board when no orchestrator is available", async () => {
+ renderKill();
+
+ await userEvent.click(screen.getByRole("button", { name: "Kill session" }));
+ await userEvent.click(screen.getByRole("button", { name: "Confirm kill" }));
+
+ await waitFor(() => {
+ expect(onKilledMock).toHaveBeenCalledWith("proj-1", undefined);
+ });
+ });
});
diff --git a/frontend/src/renderer/components/ShellTopbar.tsx b/frontend/src/renderer/components/ShellTopbar.tsx
index 892fcabbd5..d1e4310e15 100644
--- a/frontend/src/renderer/components/ShellTopbar.tsx
+++ b/frontend/src/renderer/components/ShellTopbar.tsx
@@ -195,7 +195,22 @@ export function ShellTopbar() {
) : null}
{/* Kill control sits beside the orchestrator link for active workers —
moved here from the inspector's Summary "Danger zone". */}
- {!isOrchestrator && session && sessionIsActive(session) ? : null}
+ {!isOrchestrator && session && sessionIsActive(session) ? (
+ {
+ if (orchestratorId) {
+ void navigate({
+ to: "/projects/$projectId/sessions/$sessionId",
+ params: { projectId: workspaceId, sessionId: orchestratorId },
+ });
+ return;
+ }
+ void navigate({ to: "/projects/$projectId", params: { projectId: workspaceId } });
+ }}
+ />
+ ) : null}
{!isOrchestrator && (