diff --git a/cli/src/shared/ui.ts b/cli/src/shared/ui.ts index 967d5aa1..284b631d 100644 --- a/cli/src/shared/ui.ts +++ b/cli/src/shared/ui.ts @@ -26,19 +26,29 @@ export function logStep(msg: string): void { process.stderr.write(`${CYAN}${msg}${NC}\n`); } +// Shared readline interface — reused across prompt() calls to avoid Bun's +// issue where repeatedly creating/closing interfaces on the same stdin causes +// the "close" event to fire immediately on subsequent interfaces (#1707). +let sharedRl: ReturnType | null = null; + +function getReadlineInterface(): ReturnType { + if (!sharedRl) { + sharedRl = createInterface({ input: process.stdin, output: process.stderr }); + sharedRl.on("close", () => { sharedRl = null; }); + } + return sharedRl; +} + /** Prompt for a line of user input. Throws if non-interactive. */ export async function prompt(question: string): Promise { if (process.env.SPAWN_NON_INTERACTIVE === "1") { throw new Error("Cannot prompt: SPAWN_NON_INTERACTIVE is set"); } - const rl = createInterface({ input: process.stdin, output: process.stderr }); - return new Promise((resolve, reject) => { + const rl = getReadlineInterface(); + return new Promise((resolve) => { rl.question(question, (answer) => { - rl.close(); resolve(answer.trim()); }); - rl.on("error", reject); - rl.on("close", () => resolve("")); }); }