From 64c75a53ab29caf408a0fad1f8ae8c8d19565a17 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 26 Aug 2026 14:37:08 +0200 Subject: [PATCH] fix(vscode): handle fragmented CLI startup output --- .../fix-vscode-server-startup-output.md | 5 ++ .../services/cli-backend/server-manager.ts | 12 +++-- .../src/services/cli-backend/server-utils.ts | 11 ++++- .../tests/unit/server-manager-utils.test.ts | 47 ++++++++++++++++++- 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 .changeset/fix-vscode-server-startup-output.md diff --git a/.changeset/fix-vscode-server-startup-output.md b/.changeset/fix-vscode-server-startup-output.md new file mode 100644 index 0000000000..e9d62c0255 --- /dev/null +++ b/.changeset/fix-vscode-server-startup-output.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Prevent intermittent server connection failures during VS Code startup. diff --git a/packages/kilo-vscode/src/services/cli-backend/server-manager.ts b/packages/kilo-vscode/src/services/cli-backend/server-manager.ts index 9edea2476c..8f6e776c9e 100644 --- a/packages/kilo-vscode/src/services/cli-backend/server-manager.ts +++ b/packages/kilo-vscode/src/services/cli-backend/server-manager.ts @@ -6,7 +6,7 @@ import * as path from "path" import * as vscode from "vscode" import { resolveLocalBwrapEnv, resolveTreeSitterEnv } from "./cli-resources" import { t } from "./i18n" -import { parseServerPort } from "./server-utils" +import { scanServerPort } from "./server-utils" export interface ServerInstance { port: number @@ -15,6 +15,7 @@ export interface ServerInstance { } const STARTUP_TIMEOUT_SECONDS = 30 +const STARTUP_OUTPUT_LIMIT = 1024 type WorkspaceFolderLike = { uri: { fsPath: string } } type ServerExitListener = (code: number | null, signal: NodeJS.Signals | null) => void @@ -162,13 +163,16 @@ export class ServerManager { console.log("[Kilo New] ServerManager: 📦 Process spawned with PID:", serverProcess.pid) let resolved = false + let output = "" const stderrLines: string[] = [] serverProcess.stdout?.on("data", (data: Buffer) => { - const output = data.toString() - console.log("[Kilo New] ServerManager: 📥 CLI Server stdout:", output) + const chunk = data.toString() + console.log("[Kilo New] ServerManager: 📥 CLI Server stdout:", chunk) - const port = parseServerPort(output) + const state = scanServerPort(output, chunk, STARTUP_OUTPUT_LIMIT) + output = state.output + const port = state.port if (port !== null && !resolved) { resolved = true console.log("[Kilo New] ServerManager: 🎯 Port detected:", port) diff --git a/packages/kilo-vscode/src/services/cli-backend/server-utils.ts b/packages/kilo-vscode/src/services/cli-backend/server-utils.ts index 0daf711d02..bedad7d1db 100644 --- a/packages/kilo-vscode/src/services/cli-backend/server-utils.ts +++ b/packages/kilo-vscode/src/services/cli-backend/server-utils.ts @@ -3,8 +3,15 @@ * Matches lines like: "kilo server listening on http://127.0.0.1:12345" * Returns the port number or null if not found. */ -export function parseServerPort(output: string): number | null { - const match = output.match(/listening on http:\/\/[\w.]+:(\d+)/) +export function parseServerPort(output: string, complete = false): number | null { + const match = output.match( + complete ? /listening on http:\/\/[\w.]+:(\d+)\r?\n/ : /listening on http:\/\/[\w.]+:(\d+)/, + ) if (!match) return null return parseInt(match[1]!, 10) } + +export function scanServerPort(output: string, chunk: string, limit: number) { + const text = `${output}${chunk}` + return { output: text.slice(-limit), port: parseServerPort(text, true) } +} diff --git a/packages/kilo-vscode/tests/unit/server-manager-utils.test.ts b/packages/kilo-vscode/tests/unit/server-manager-utils.test.ts index c62e3b51eb..9380e9724d 100644 --- a/packages/kilo-vscode/tests/unit/server-manager-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/server-manager-utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test" -import { parseServerPort } from "../../src/services/cli-backend/server-utils" +import { parseServerPort, scanServerPort } from "../../src/services/cli-backend/server-utils" import { resolveServerCwd, resolveIndexingEnv, @@ -61,6 +61,51 @@ describe("parseServerPort", () => { const output = "listening on http://127.0.0.1:3000 and http://127.0.0.1:4000" expect(parseServerPort(output)).toBe(3000) }) + + it("waits for the complete startup line before resolving a split port", () => { + const first = "kilo server listening on http://127.0.0.1:43" + + expect(parseServerPort(first, true)).toBeNull() + expect(parseServerPort(`${first}123\n`, true)).toBe(43123) + }) + + it("detects a startup announcement split across stdout chunks", () => { + const first = "kilo server listening on http://127.0." + const second = "0.1:43123\n" + + expect(parseServerPort(first, true)).toBeNull() + expect(parseServerPort(`${first}${second}`, true)).toBe(43123) + }) + + it("accepts complete Windows startup lines", () => { + expect(parseServerPort("kilo server listening on http://127.0.0.1:43123\r\n", true)).toBe(43123) + }) +}) + +describe("scanServerPort", () => { + it("detects startup announcements split across stdout chunks", () => { + const first = scanServerPort("", "kilo server listening on http://127.0.", 1024) + const second = scanServerPort(first.output, "0.1:43123\n", 1024) + + expect(first.port).toBeNull() + expect(second.port).toBe(43123) + }) + + it("waits for split port digits before resolving startup", () => { + const first = scanServerPort("", "kilo server listening on http://127.0.0.1:43", 1024) + const second = scanServerPort(first.output, "123\n", 1024) + + expect(first.port).toBeNull() + expect(second.port).toBe(43123) + }) + + it("preserves startup announcements followed by oversized stdout chunks", () => { + const chunk = `kilo server listening on http://127.0.0.1:43123\n${"x".repeat(1024)}` + const state = scanServerPort("", chunk, 1024) + + expect(state.port).toBe(43123) + expect(state.output).toHaveLength(1024) + }) }) describe("cli tree-sitter resources", () => {