fix(cli-run): attach to default server when auto port range exhausted

This commit is contained in:
YeonGyu-Kim
2026-02-18 16:02:57 +09:00
parent ada8c127aa
commit dacada152a
2 changed files with 51 additions and 1 deletions

View File

@@ -177,6 +177,28 @@ describe("createServerConnection", () => {
expect(mockServerClose).toHaveBeenCalledTimes(1)
})
it("auto mode attaches to default server when port range is exhausted", async () => {
// given
const signal = new AbortController().signal
mockGetAvailableServerPort.mockRejectedValueOnce(
new Error("No available port found in range 4097-4116"),
)
mockIsPortAvailable.mockResolvedValueOnce(false)
// when
const result = await createServerConnection({ signal })
// then
expect(mockGetAvailableServerPort).toHaveBeenCalledWith(4096, "127.0.0.1")
expect(mockIsPortAvailable).toHaveBeenCalledWith(4096, "127.0.0.1")
expect(mockCreateOpencodeClient).toHaveBeenCalledWith({
baseUrl: "http://127.0.0.1:4096",
})
expect(mockCreateOpencode).not.toHaveBeenCalled()
result.cleanup()
expect(mockServerClose).not.toHaveBeenCalled()
})
it("invalid port throws error", async () => {
// given
const signal = new AbortController().signal

View File

@@ -12,6 +12,14 @@ function isPortStartFailure(error: unknown, port: number): boolean {
return error.message.includes(`Failed to start server on port ${port}`)
}
function isPortRangeExhausted(error: unknown): boolean {
if (!(error instanceof Error)) {
return false
}
return error.message.includes("No available port found in range")
}
async function startServer(options: { signal: AbortSignal, port: number }): Promise<ServerConnection> {
const { signal, port } = options
const { client, server } = await withWorkingOpencodePath(() =>
@@ -67,7 +75,27 @@ export async function createServerConnection(options: {
return { client, cleanup: () => {} }
}
const { port: selectedPort, wasAutoSelected } = await getAvailableServerPort(DEFAULT_SERVER_PORT, "127.0.0.1")
let selectedPort: number
let wasAutoSelected: boolean
try {
const selected = await getAvailableServerPort(DEFAULT_SERVER_PORT, "127.0.0.1")
selectedPort = selected.port
wasAutoSelected = selected.wasAutoSelected
} catch (error) {
if (!isPortRangeExhausted(error)) {
throw error
}
const defaultPortIsAvailable = await isPortAvailable(DEFAULT_SERVER_PORT, "127.0.0.1")
if (defaultPortIsAvailable) {
throw error
}
console.log(pc.dim("Port range exhausted, attaching to existing server on"), pc.cyan(DEFAULT_SERVER_PORT.toString()))
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${DEFAULT_SERVER_PORT}` })
return { client, cleanup: () => {} }
}
if (wasAutoSelected) {
console.log(pc.dim("Auto-selected port"), pc.cyan(selectedPort.toString()))
} else {