Skip to content

Commit 658ba5b

Browse files
authored
fix(desktop): avoid unhandled launchd readiness rejection (#572)
* fix(desktop): avoid unhandled launchd readiness rejection * fix(desktop): handle attach readiness and test contract
1 parent 6c66f84 commit 658ba5b

File tree

3 files changed

+27
-10
lines changed

3 files changed

+27
-10
lines changed

apps/desktop/main/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,13 @@ async function runLaunchdColdStart(): Promise<void> {
599599
diagnosticsReporter?.markColdStartRunning(
600600
"waiting for controller readiness",
601601
);
602-
await launchdResult.controllerReady;
602+
}
603+
604+
const controllerReady = await launchdResult.controllerReady;
605+
if (!controllerReady.ok) {
606+
throw controllerReady.error;
607+
}
608+
if (!launchdResult.isAttach) {
603609
logColdStart("controller ready");
604610
}
605611

apps/desktop/main/services/launchd-bootstrap.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export interface LaunchdBootstrapResult {
8383
controller: string;
8484
openclaw: string;
8585
};
86-
/** Promise that resolves when controller is ready (for optional awaiting) */
87-
controllerReady: Promise<void>;
86+
/** Promise that always settles with controller readiness outcome. */
87+
controllerReady: Promise<ControllerReadyResult>;
8888
/** Actual ports used (may differ from requested if OS-assigned or recovered) */
8989
effectivePorts: {
9090
controllerPort: number;
@@ -95,6 +95,8 @@ export interface LaunchdBootstrapResult {
9595
isAttach: boolean;
9696
}
9797

98+
type ControllerReadyResult = { ok: true } | { ok: false; error: Error };
99+
98100
/** Metadata persisted between sessions for attach discovery */
99101
interface RuntimePortsMetadata {
100102
writtenAt: string;
@@ -595,11 +597,20 @@ export async function bootstrapWithLaunchd(
595597
);
596598

597599
// Controller readiness
598-
const controllerReady = needsControllerReady
599-
? waitForControllerReadiness(effectivePorts.controllerPort).then(() =>
600-
console.log("Controller is ready"),
601-
)
602-
: Promise.resolve();
600+
const controllerReady: Promise<ControllerReadyResult> = needsControllerReady
601+
? waitForControllerReadiness(effectivePorts.controllerPort)
602+
.then(() => {
603+
console.log("Controller is ready");
604+
return { ok: true } as const;
605+
})
606+
.catch((error: unknown) => ({
607+
ok: false,
608+
error:
609+
error instanceof Error
610+
? error
611+
: new Error(`Controller readiness failed: ${String(error)}`),
612+
}))
613+
: Promise.resolve({ ok: true });
603614

604615
// Persist port metadata
605616
await writeRuntimePorts(plistDir, {

tests/desktop/launchd-startup-scenarios.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ describe("Launchd Startup Scenarios", () => {
807807
const result = await bootstrapWithLaunchd(makeBootstrapEnv() as never);
808808

809809
// controllerReady should be a promise that resolves (fetch mock returns 200)
810-
await expect(result.controllerReady).resolves.toBeUndefined();
810+
await expect(result.controllerReady).resolves.toEqual({ ok: true });
811811
});
812812

813813
// -----------------------------------------------------------------------
@@ -831,7 +831,7 @@ describe("Launchd Startup Scenarios", () => {
831831
const result = await bootstrapWithLaunchd(makeBootstrapEnv() as never);
832832

833833
// controllerReady should resolve immediately (already healthy)
834-
await expect(result.controllerReady).resolves.toBeUndefined();
834+
await expect(result.controllerReady).resolves.toEqual({ ok: true });
835835
expect(result.isAttach).toBe(true);
836836
});
837837

0 commit comments

Comments
 (0)