diff --git a/packages/pi-coding-agent/src/core/lsp/client.ts b/packages/pi-coding-agent/src/core/lsp/client.ts index 7431a2014..4744494ea 100644 --- a/packages/pi-coding-agent/src/core/lsp/client.ts +++ b/packages/pi-coding-agent/src/core/lsp/client.ts @@ -842,7 +842,17 @@ export async function sendNotification(client: LspClient, method: string, params }; client.lastActivity = Date.now(); - await writeMessage(client.proc.stdin, notification); + try { + await writeMessage(client.proc.stdin, notification); + } catch (err: unknown) { + // EPIPE means the LSP process died (e.g. after lsp.reload killed it). + // Swallow so callers don't crash — the next getOrCreateClient call + // will spawn a fresh server (#815). + if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EPIPE') { + return; + } + throw err; + } } /** diff --git a/packages/pi-coding-agent/src/core/lsp/index.ts b/packages/pi-coding-agent/src/core/lsp/index.ts index 05f6f6934..b2dbf2c6d 100644 --- a/packages/pi-coding-agent/src/core/lsp/index.ts +++ b/packages/pi-coding-agent/src/core/lsp/index.ts @@ -211,6 +211,13 @@ async function reloadServer(client: LspClient, serverName: string, signal?: Abor } if (output.startsWith("Restarted")) { client.proc.kill(); + // Wait for the process to actually exit so the crash recovery handler + // removes the client from the cache. Without this, the next + // getOrCreateClient call may return the dead client (#815). + await Promise.race([ + client.proc.exited, + new Promise(r => setTimeout(r, 3000)), + ]); } return output; }