From 01c8705b507d3d9e8c386c575513020501030110 Mon Sep 17 00:00:00 2001 From: Vaibhaav Date: Fri, 19 Jun 2026 16:29:28 +0530 Subject: [PATCH 1/2] fix: remove projects with incomplete session handles --- backend/internal/service/session/service.go | 11 +++++++++++ backend/internal/service/session/service_test.go | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/backend/internal/service/session/service.go b/backend/internal/service/session/service.go index bbcb32c1..6b58bc09 100644 --- a/backend/internal/service/session/service.go +++ b/backend/internal/service/session/service.go @@ -366,6 +366,9 @@ func (s *Service) TeardownProject(ctx context.Context, project domain.ProjectID) continue } if _, err := s.Kill(ctx, rec.ID); err != nil { + if isIncompleteHandleError(err) { + continue + } return err } } @@ -373,6 +376,14 @@ func (s *Service) TeardownProject(ctx context.Context, project domain.ProjectID) return err } +func isIncompleteHandleError(err error) bool { + if errors.Is(err, sessionmanager.ErrIncompleteHandle) { + return true + } + var apiErr *apierr.Error + return errors.As(err, &apiErr) && apiErr.Code == "SESSION_INCOMPLETE_HANDLE" +} + // List returns sessions as enriched display models after applying API filters. func (s *Service) List(ctx context.Context, filter ListFilter) ([]domain.Session, error) { recs, err := s.listRecords(ctx, filter.ProjectID) diff --git a/backend/internal/service/session/service_test.go b/backend/internal/service/session/service_test.go index ee114108..af218c52 100644 --- a/backend/internal/service/session/service_test.go +++ b/backend/internal/service/session/service_test.go @@ -235,6 +235,20 @@ func TestTeardownProjectStopsOnKillError(t *testing.T) { } } +func TestTeardownProjectContinuesOnIncompleteHandle(t *testing.T) { + st := newFakeStore() + st.sessions["mer-1"] = domain.SessionRecord{ID: "mer-1", ProjectID: "mer"} + fc := &fakeCommander{killErr: sessionmanager.ErrIncompleteHandle} + svc := &Service{manager: fc, store: st} + + if err := svc.TeardownProject(context.Background(), "mer"); err != nil { + t.Fatalf("TeardownProject: %v", err) + } + if len(fc.cleanupProjects) != 1 || fc.cleanupProjects[0] != "mer" { + t.Fatalf("cleanup projects = %#v, want [mer]", fc.cleanupProjects) + } +} + func TestSpawnOrchestratorCleanKillsActiveOrchestratorsBeforeSpawn(t *testing.T) { st := newFakeStore() st.projects["mer"] = domain.ProjectRecord{ID: "mer"} From 2da063bf58fd8bf4f27bdcf0793674ad2abee9e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Jun 2026 11:05:07 +0000 Subject: [PATCH 2/2] chore: format with prettier [skip ci] --- frontend/src/main.ts | 4 +++- frontend/src/renderer/components/TelemetryBoundary.tsx | 4 +++- frontend/src/renderer/lib/telemetry.test.ts | 6 +----- frontend/src/renderer/lib/telemetry.ts | 7 ++----- frontend/src/shared/telemetry.test.ts | 6 +++++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 112cb75a..6390f887 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -320,7 +320,9 @@ ipcMain.handle("daemon:getStatus", () => daemonStatus); ipcMain.handle("daemon:start", () => startDaemon()); ipcMain.handle("daemon:stop", () => stopDaemon()); ipcMain.handle("app:getVersion", () => app.getVersion()); -ipcMain.handle("telemetry:getBootstrap", () => buildTelemetryBootstrap(process.env, app.getVersion(), process.platform)); +ipcMain.handle("telemetry:getBootstrap", () => + buildTelemetryBootstrap(process.env, app.getVersion(), process.platform), +); ipcMain.handle("app:chooseDirectory", async () => { const options: OpenDialogOptions = { properties: ["openDirectory"], diff --git a/frontend/src/renderer/components/TelemetryBoundary.tsx b/frontend/src/renderer/components/TelemetryBoundary.tsx index 3868a77d..9ed73c07 100644 --- a/frontend/src/renderer/components/TelemetryBoundary.tsx +++ b/frontend/src/renderer/components/TelemetryBoundary.tsx @@ -29,7 +29,9 @@ export class TelemetryBoundary extends React.Component {

The app hit an unexpected error.

-

Restart the app or check the daemon logs if this keeps happening.

+

+ Restart the app or check the daemon logs if this keeps happening. +

); diff --git a/frontend/src/renderer/lib/telemetry.test.ts b/frontend/src/renderer/lib/telemetry.test.ts index da7de90e..3b8d9f6f 100644 --- a/frontend/src/renderer/lib/telemetry.test.ts +++ b/frontend/src/renderer/lib/telemetry.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from "vitest"; -import { - routeSurface, - sanitizeRendererExceptionProperties, - sanitizeRendererProperties, -} from "./telemetry"; +import { routeSurface, sanitizeRendererExceptionProperties, sanitizeRendererProperties } from "./telemetry"; describe("telemetry sanitizers", () => { it("categorizes routes without exporting raw paths", () => { diff --git a/frontend/src/renderer/lib/telemetry.ts b/frontend/src/renderer/lib/telemetry.ts index e03e853e..cb66a741 100644 --- a/frontend/src/renderer/lib/telemetry.ts +++ b/frontend/src/renderer/lib/telemetry.ts @@ -70,7 +70,7 @@ export async function sanitizeRendererProperties( case "ao.renderer.orchestrator_open_requested": { const projectIDHash = await hashedTelemetryID(properties?.project_id); if (projectIDHash) safe.project_id_hash = projectIDHash; - break + break; } } return safe; @@ -157,10 +157,7 @@ export async function captureRendererEvent(event: string, properties?: Record, -): Promise { +export async function captureRendererException(error: unknown, properties?: Record): Promise { if (!(await initTelemetry())) return; const safeProperties = await sanitizeRendererExceptionProperties(error, properties); posthog.capture("ao.renderer.exception", safeProperties); diff --git a/frontend/src/shared/telemetry.test.ts b/frontend/src/shared/telemetry.test.ts index 8cb4323e..f3684dd5 100644 --- a/frontend/src/shared/telemetry.test.ts +++ b/frontend/src/shared/telemetry.test.ts @@ -7,7 +7,11 @@ import { buildTelemetryBootstrap, defaultDataDir, loadOrCreateTelemetryInstallId const tempDirs: string[] = []; afterEach(async () => { - await Promise.all(tempDirs.splice(0).map((dir) => import("node:fs/promises").then(({ rm }) => rm(dir, { recursive: true, force: true })))); + await Promise.all( + tempDirs + .splice(0) + .map((dir) => import("node:fs/promises").then(({ rm }) => rm(dir, { recursive: true, force: true }))), + ); }); test("defaultDataDir prefers AO_DATA_DIR", () => {