fix(cli-run): retry server start on port binding race condition

When port appears available but binding fails (race with another opencode
instance), retry on next available port (auto mode) or attach to existing
server (explicit port mode) instead of crashing with exit code 1.
This commit is contained in:
YeonGyu-Kim
2026-02-18 13:45:41 +09:00
parent 98c79b687c
commit b6094432b6
2 changed files with 90 additions and 10 deletions
+46 -10
View File
@@ -5,6 +5,24 @@ import { getAvailableServerPort, isPortAvailable, DEFAULT_SERVER_PORT } from "..
import { withWorkingOpencodePath } from "./opencode-binary-resolver"
import { prependResolvedOpencodeBinToPath } from "./opencode-bin-path"
function isPortStartFailure(error: unknown, port: number): boolean {
if (!(error instanceof Error)) {
return false
}
return error.message.includes(`Failed to start server on port ${port}`)
}
async function startServer(options: { signal: AbortSignal, port: number }): Promise<ServerConnection> {
const { signal, port } = options
const { client, server } = await withWorkingOpencodePath(() =>
createOpencode({ signal, port, hostname: "127.0.0.1" }),
)
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
return { client, cleanup: () => server.close() }
}
export async function createServerConnection(options: {
port?: number
attach?: string
@@ -29,11 +47,22 @@ export async function createServerConnection(options: {
if (available) {
console.log(pc.dim("Starting server on port"), pc.cyan(port.toString()))
const { client, server } = await withWorkingOpencodePath(() =>
createOpencode({ signal, port, hostname: "127.0.0.1" }),
)
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
return { client, cleanup: () => server.close() }
try {
return await startServer({ signal, port })
} catch (error) {
if (!isPortStartFailure(error, port)) {
throw error
}
const stillAvailable = await isPortAvailable(port, "127.0.0.1")
if (stillAvailable) {
throw error
}
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("became occupied, attaching to existing server"))
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
return { client, cleanup: () => {} }
}
}
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("is occupied, attaching to existing server"))
@@ -47,9 +76,16 @@ export async function createServerConnection(options: {
} else {
console.log(pc.dim("Starting server on port"), pc.cyan(selectedPort.toString()))
}
const { client, server } = await withWorkingOpencodePath(() =>
createOpencode({ signal, port: selectedPort, hostname: "127.0.0.1" }),
)
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
return { client, cleanup: () => server.close() }
try {
return await startServer({ signal, port: selectedPort })
} catch (error) {
if (!isPortStartFailure(error, selectedPort)) {
throw error
}
const { port: retryPort } = await getAvailableServerPort(selectedPort + 1, "127.0.0.1")
console.log(pc.dim("Retrying server start on port"), pc.cyan(retryPort.toString()))
return await startServer({ signal, port: retryPort })
}
}