fix(core): restore detached hub port fallback

This commit is contained in:
Saoud Rizwan
2026-06-06 19:13:25 -07:00
parent 54cd7fa3ee
commit 7f185014bf
2 changed files with 56 additions and 10 deletions
+40 -8
View File
@@ -101,16 +101,21 @@ describe("ensureDetachedHubServer", () => {
}
});
it("starts the daemon on the resolved default hub port", async () => {
it("lets the daemon bind port 0 when the default endpoint is occupied", async () => {
readHubDiscovery.mockResolvedValueOnce(undefined).mockResolvedValueOnce({
url: "ws://127.0.0.1:25463/hub",
url: "ws://127.0.0.1:5555/hub",
buildId: "current-build",
authToken: "new-token",
});
probeHubServer.mockResolvedValueOnce(undefined).mockResolvedValueOnce({
url: "ws://127.0.0.1:25463/hub",
buildId: "current-build",
});
probeHubServer
.mockResolvedValueOnce({
url: "ws://127.0.0.1:25463/hub",
buildId: "current-build",
})
.mockResolvedValueOnce({
url: "ws://127.0.0.1:5555/hub",
buildId: "current-build",
});
verifyHubConnection.mockResolvedValueOnce(true);
const { ensureDetachedHubServer } = await import(".");
@@ -123,15 +128,42 @@ describe("ensureDetachedHubServer", () => {
| undefined;
expect(result).toEqual({
url: "ws://127.0.0.1:25463/hub",
url: "ws://127.0.0.1:5555/hub",
authToken: "new-token",
});
expect(spawn).toHaveBeenCalledOnce();
expect(spawnArgs).toContain("--port");
expect(spawnArgs).toContain("25463");
expect(spawnArgs).toContain("0");
expect(spawnOptions?.env?.[CLINE_RUN_AS_HUB_DAEMON_ENV]).toBe("1");
});
it("keeps an explicit detached hub port fixed", async () => {
readHubDiscovery.mockResolvedValueOnce(undefined).mockResolvedValueOnce({
url: "ws://127.0.0.1:31000/hub",
buildId: "current-build",
authToken: "new-token",
});
probeHubServer.mockResolvedValueOnce(undefined).mockResolvedValueOnce({
url: "ws://127.0.0.1:31000/hub",
buildId: "current-build",
});
verifyHubConnection.mockResolvedValueOnce(true);
const { ensureDetachedHubServer } = await import(".");
const result = await ensureDetachedHubServer("/workspace", { port: 31000 });
const spawnCalls = (spawn as unknown as { mock: { calls: unknown[][] } })
.mock.calls;
const spawnArgs = spawnCalls[0]?.[1] as string[] | undefined;
expect(result).toEqual({
url: "ws://127.0.0.1:31000/hub",
authToken: "new-token",
});
expect(spawn).toHaveBeenCalledOnce();
expect(spawnArgs).toContain("--port");
expect(spawnArgs).toContain("31000");
});
it("retries a transient ETXTBSY spawn failure while starting the detached daemon", async () => {
vi.useFakeTimers();
try {
+16 -2
View File
@@ -206,6 +206,8 @@ export function prewarmDetachedHubServer(
return;
}
const owner = resolveSharedHubOwnerContext();
const hasExplicitPort =
endpoint.port !== undefined || !!process.env.CLINE_HUB_PORT?.trim();
const resolvedEndpoint = resolveHubEndpointOptions(endpoint);
const expectedUrl = createHubServerUrl(
resolvedEndpoint.host,
@@ -238,7 +240,12 @@ export function prewarmDetachedHubServer(
if (expected?.url) {
await retireIncompatibleHub(expected, owner.discoveryPath);
}
await spawnDetachedHubServerWithRetry(workspaceRoot, resolvedEndpoint);
const shouldUseFallbackPort =
!hasExplicitPort && resolvedEndpoint.port !== 0;
const spawnEndpoint = shouldUseFallbackPort
? { ...resolvedEndpoint, port: 0 }
: resolvedEndpoint;
await spawnDetachedHubServerWithRetry(workspaceRoot, spawnEndpoint);
})
.catch(() => {
// best-effort prewarm only
@@ -260,6 +267,9 @@ export async function ensureDetachedHubServer(
endpointOverrides.port !== undefined ||
endpointOverrides.pathname !== undefined ||
!!process.env.CLINE_HUB_PORT?.trim();
const hasExplicitPort =
endpointOverrides.port !== undefined ||
!!process.env.CLINE_HUB_PORT?.trim();
const endpoint = resolveHubEndpointOptions(endpointOverrides);
const expectedUrl = createHubServerUrl(
endpoint.host,
@@ -302,7 +312,11 @@ export async function ensureDetachedHubServer(
if (expected?.url) {
await retireIncompatibleHub(expected, owner.discoveryPath);
}
await spawnDetachedHubServerWithRetry(workspaceRoot, endpoint);
const shouldUseFallbackPort = !hasExplicitPort && endpoint.port !== 0;
const spawnEndpoint = shouldUseFallbackPort
? { ...endpoint, port: 0 }
: endpoint;
await spawnDetachedHubServerWithRetry(workspaceRoot, spawnEndpoint);
const deadline = Date.now() + HUB_STARTUP_TIMEOUT_MS;
while (Date.now() < deadline) {
const nextDiscovery = await readHubDiscovery(owner.discoveryPath);