From b9a44314321b71e51aaa164b94f271c2accf23bd Mon Sep 17 00:00:00 2001 From: johannesjo Date: Sat, 16 May 2026 13:23:29 +0200 Subject: [PATCH] fix(release): read release-notes prompt from /dev/tty to avoid EAGAIN npm run wrapping leaves stdin in non-blocking mode while still reporting isTTY=true, so fs.readSync(0, ...) threw EAGAIN and crashed `npm run version` at the "Generate release notes via AI?" prompt. Read from /dev/tty (always blocking) when available, and fall back to stdin with an Atomics.wait retry on EAGAIN for platforms without /dev/tty. --- tools/release-notes.js | 53 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/tools/release-notes.js b/tools/release-notes.js index 5a57ab7cc9..5beea00393 100644 --- a/tools/release-notes.js +++ b/tools/release-notes.js @@ -270,21 +270,54 @@ const writeFileEnsuringDir = (filePath, content) => { }; const readLineSync = () => { + // Open /dev/tty directly when available: stdin (fd 0) may be in non-blocking + // mode when this script is invoked through `npm run`, which makes + // fs.readSync(0, ...) throw EAGAIN immediately. /dev/tty is always blocking. + let fd = 0; + let opened = false; + try { + fd = fs.openSync('/dev/tty', 'r'); + opened = true; + } catch { + fd = 0; + } + const buffer = Buffer.alloc(1); const chars = []; + const sharedBuf = new SharedArrayBuffer(4); + const waitView = new Int32Array(sharedBuf); - while (true) { - const bytesRead = fs.readSync(0, buffer, 0, 1, null); - if (bytesRead === 0) { - break; - } + try { + while (true) { + let bytesRead; + try { + bytesRead = fs.readSync(fd, buffer, 0, 1, null); + } catch (err) { + if (err.code === 'EAGAIN') { + Atomics.wait(waitView, 0, 0, 20); + continue; + } + throw err; + } + if (bytesRead === 0) { + break; + } - const char = buffer.toString('utf8', 0, bytesRead); - if (char === '\n') { - break; + const char = buffer.toString('utf8', 0, bytesRead); + if (char === '\n') { + break; + } + if (char !== '\r') { + chars.push(char); + } } - if (char !== '\r') { - chars.push(char); + } finally { + if (opened) { + try { + fs.closeSync(fd); + } catch { + /* ignore */ + } } }