Compare commits

...
Author SHA1 Message Date
alvinfeiandCursor 65e6323a8c fix(memhub): limit disabled-service fallback to app lifetime
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 23:46:24 +08:00
kristinhe 0085017635 test(desktop): cover the disabled-service install error classification
Pin both directions of the classification: the durable disable state
degrades, and every other install failure keeps failing closed. The
non-Error case matters because the guard must not match a bare string.
2026-10-16 23:44:41 +08:00
kristinhe e810c168f8 fix(desktop): keep starting when the runtime service is disabled by system
A durable `launchctl disable` override outlives a re-install, and the
installer deliberately refuses to re-enable a service the user or the
system disabled. Turning that refusal into a fatal startup error leaves
the app unable to open at all, with no surface left to re-enable it.

Degrade instead: bring the Runtime up as a child process, the same path
the non-managed mode already uses, report the durable state, and leave
the login-time service disabled. Every other install failure still fails
closed.
2026-10-16 23:44:41 +08:00
kristinhe b2e2a54e73 fix(desktop): classify the durable disabled-service install error
installUserService reports a persistent `launchctl disable` override with
the exact code `runtime_service_disabled_by_system`. Expose a single
place that recognises it, so the startup path can tell a durable OS state
apart from an ordinary install failure.
2026-10-16 23:44:41 +08:00
3 changed files with 177 additions and 15 deletions

No files matched your search

+40 -15
View File
@@ -103,6 +103,7 @@ import { createSourceChangeHandler } from "./source-change-handler";
import { SourceDirectoryPicker } from "./source-directory-picker";
import {
installBundledCliIfNeeded,
installServiceWithDisabledFallback,
isInstalledApplicationsApp,
stableRuntimeErrorCode,
startStableRuntimeRepair,
@@ -989,6 +990,7 @@ async function initialize(isQuitting: () => boolean): Promise<void> {
const startAtLogin = owner?.owner === "app" ? owner.start_at_login : true;
const previousOwner = owner;
let runtimeChanged = false;
let transientRuntime = false;
if (owner?.owner !== "cli") {
const installed = await installPackagedRuntime({
resourcesPath: process.resourcesPath,
@@ -1027,25 +1029,48 @@ async function initialize(isQuitting: () => boolean): Promise<void> {
});
}
if (!alreadyRunning) {
try {
await installUserService(layout, {
uid: process.getuid?.(),
platform: "darwin",
});
await waitForRuntimeProcess({ dataDir, attempts: 360 });
} catch (error) {
if (owner?.owner === "app") {
if (previousOwner === null)
await removeRuntimeServiceOwner(dataDir);
else await writeRuntimeServiceOwner(dataDir, previousOwner);
}
throw error;
}
// A durable `launchctl disable` override is a state to report, not a
// failure to undo: `installUserService` never re-enables a service the
// user or the system disabled. An explicit App launch may still own a
// transient Runtime for this UI session. Shutdown stops that process.
const serviceMode = await installServiceWithDisabledFallback({
install: async () => {
await installUserService(layout, {
uid: process.getuid?.(),
platform: "darwin",
});
await waitForRuntimeProcess({ dataDir, attempts: 360 });
},
restoreOwner: async () => {
if (owner?.owner === "app") {
if (previousOwner === null)
await removeRuntimeServiceOwner(dataDir);
else await writeRuntimeServiceOwner(dataDir, previousOwner);
}
owner = previousOwner;
},
reportDisabled: () =>
diagnostics.log({
level: "warn",
component: "desktop.stable_runtime",
event: "stable_runtime.service_disabled",
outcome: "degraded",
error_code: "runtime_service_disabled_by_system",
}),
startTransient: async () => {
await ensureRuntimeProcess({
dataDir,
launch: runtimeLaunch(),
attempts: app.isPackaged ? 360 : 120,
});
},
});
transientRuntime = serviceMode === "transient";
}
if (owner?.owner === "app" && !startAtLogin) {
await disableRuntimeService(dataDir);
}
preserveManagedRuntimeOnQuit = true;
preserveManagedRuntimeOnQuit = !transientRuntime;
} else if (standaloneRuntime) {
await ensureRuntimeProcess({
dataDir,
@@ -12,6 +12,8 @@ import { describe, expect, it, vi } from "vitest";
import {
ensureStableRuntimeThenRepair,
installBundledCliIfNeeded,
installServiceWithDisabledFallback,
isDisabledServiceInstallError,
isInstalledApplicationsApp,
stableRuntimeErrorCode,
startStableRuntimeRepair,
@@ -158,3 +160,97 @@ describe("ensureStableRuntimeThenRepair", () => {
);
});
});
describe("isDisabledServiceInstallError", () => {
it("recognises the durable disabled-service state", () => {
expect(
isDisabledServiceInstallError(
new Error("runtime_service_disabled_by_system"),
),
).toBe(true);
});
it("keeps every other startup failure fatal", () => {
for (const other of [
new Error("runtime_service_label_conflict"),
new Error("runtime_service_install_root_not_ready"),
new Error("runtime_service_command_failed"),
"runtime_service_disabled_by_system",
null,
undefined,
]) {
expect(isDisabledServiceInstallError(other)).toBe(false);
}
});
});
describe("installServiceWithDisabledFallback", () => {
it("keeps a successfully installed service managed", async () => {
const restoreOwner = vi.fn(async () => undefined);
const reportDisabled = vi.fn(async () => undefined);
const startTransient = vi.fn(async () => undefined);
await expect(
installServiceWithDisabledFallback({
install: async () => undefined,
restoreOwner,
reportDisabled,
startTransient,
}),
).resolves.toBe("managed");
expect(restoreOwner).not.toHaveBeenCalled();
expect(reportDisabled).not.toHaveBeenCalled();
expect(startTransient).not.toHaveBeenCalled();
});
it("uses an app-lifetime Runtime when launchd disabled the service", async () => {
const order: string[] = [];
await expect(
installServiceWithDisabledFallback({
install: async () => {
order.push("install");
throw new Error("runtime_service_disabled_by_system");
},
restoreOwner: async () => {
order.push("restore-owner");
},
reportDisabled: async () => {
order.push("report-start");
await Promise.resolve();
order.push("report-end");
},
startTransient: async () => {
order.push("start-transient");
},
}),
).resolves.toBe("transient");
expect(order).toEqual([
"install",
"restore-owner",
"report-start",
"report-end",
"start-transient",
]);
});
it("restores ownership and preserves unrelated startup failures", async () => {
const restoreOwner = vi.fn(async () => undefined);
const reportDisabled = vi.fn(async () => undefined);
const startTransient = vi.fn(async () => undefined);
await expect(
installServiceWithDisabledFallback({
install: async () => {
throw new Error("runtime_service_command_failed");
},
restoreOwner,
reportDisabled,
startTransient,
}),
).rejects.toThrow("runtime_service_command_failed");
expect(restoreOwner).toHaveBeenCalledOnce();
expect(reportDisabled).not.toHaveBeenCalled();
expect(startTransient).not.toHaveBeenCalled();
});
});
+41
View File
@@ -137,3 +137,44 @@ export function stableRuntimeErrorCode(error: unknown): string {
}
return "desktop_start_failed";
}
/**
* `installUserService` reports a durable `launchctl disable` override with this
* exact code. It is a state to surface, not a failure to undo: the installer
* never re-enables a service the user or the system disabled, and the macOS
* platform checks assert that refusal
* (`install_refuses_to_re_enable_a_disabled_service`).
*
* The override outlives a re-install, so treating it as fatal leaves an
* affected machine with an app that exits on every launch. Desktop degrades
* instead, owning the Runtime as a child process, and reports the durable
* state rather than clearing it.
*
* Only this exact code degrades. Every other install failure keeps failing
* closed, so a real misconfiguration still surfaces instead of hiding behind a
* child process that may not serve hosts either.
*/
export function isDisabledServiceInstallError(error: unknown): boolean {
return (
error instanceof Error &&
error.message === "runtime_service_disabled_by_system"
);
}
export async function installServiceWithDisabledFallback(options: {
install(): Promise<void>;
restoreOwner(): Promise<void>;
reportDisabled(): Promise<void>;
startTransient(): Promise<void>;
}): Promise<"managed" | "transient"> {
try {
await options.install();
return "managed";
} catch (error) {
await options.restoreOwner();
if (!isDisabledServiceInstallError(error)) throw error;
await options.reportDisabled();
await options.startTransient();
return "transient";
}
}