You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: prompt on stderr and gate OTP on stderr TTY - #10192
otplease only prompted for OTP when both stdin and stdout were TTYs, and read-user-info used read's default output (process.stdout). Spawning npm with piped stdout (e.g. stdio: ['inherit', 'pipe', 'inherit']) therefore failed with bare EOTP instead of showing an interactive OTP prompt, even though the user could still see stderr and type on stdin.
tap test/lib/utils/auth.js --no-coverage — pass (incl. new piped-stdout case)
tap test/lib/utils/read-user-info.js --no-coverage — pass
Full npm test not run locally (large; relying on CI)
No live npm trust / OTP session in this environment; covered by unit mocks matching the reporter's TTY layout
AI disclosure
This change was prepared with AI assistance (Grok Bot / Cursor agent). I reviewed the diff, updated/ran the tests above, and believe the fix matches the issue.
Interactive auth prompts (OTP/login) wrote to stdout via `read`'s
default, and otplease refused to prompt unless stdout was a TTY. When
callers pipe stdout (stdio inherit/pipe/inherit), OTP failed with EOTP
instead of prompting even though stdin and stderr were TTYs.
- Gate otplease on stdin+stderr isTTY
- Send read-user-info prompts to process.stderr
- Cover piped-stdout + TTY stderr in auth tests
Fixesnpm#10190
Pushed a follow-up: test/lib/commands/profile.jsmockProfile now also mocks process.stderr.isTTY to match the OTP gate in auth.js (prompts go to stderr). Locally tap test/lib/commands/profile.js test/lib/utils/auth.js --no-coverage is green after this.
auth.js now gates OTP prompts on stdin+stderr TTYs (prompts write to
stderr). mockProfile only mocked stdin/stdout, so enable/disable-2fa
tests failed in CI with EOTP instead of prompting.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The web-auth opener still requires a stdout TTY and writes instructions there, so piping stdout can hide authentication instructions and skip browser opening while polling continues.
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks. Fixed in ba2f4b5: openUrlPrompt no longer requires stdout to be a TTY. It now prompts whenever stdin and stderr are TTYs, and it uses stderr as the readline output. If stdout is piped, the auth URL is also printed on stderr, so the user can still see it next to the prompt. If stdout is a TTY, nothing is repeated.
I added tests in test/lib/utils/open-url.js for three cases: stdout piped with the prompt on stderr, stdout as a TTY with no duplicate URL, and stderr not a TTY with no prompt.
The browser prompt used to require stdout to be a TTY, so piping stdout (e.g. `npm publish | tee log`) skipped it. Gate the prompt on stdin/stderr instead and repeat the auth URL on stderr when stdout is not a TTY so it stays visible.
Write prompt completion newline to the prompt stream
lib/utils/read-user-info.js:17
Silent password prompts also move to stderr here, but Display.#inputHandler still terminates them with output.standard() (lib/utils/display.js:331–334). With stdout piped, the completion newline goes into the pipe while stderr remains on the password-prompt line, so subsequent stderr messages run into that prompt. Route the completion/cancellation newline to the prompt's stream as well, and cover a silent prompt with piped stdout.
Derive readline terminal mode from stdin so silent password prompts
stay muted when stderr is redirected. Always repeat the web-auth URL
on stderr when stdout is not a TTY, including JSON --no-browser mode.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Silent-prompt completion and cancellation still send newlines to stdout instead of stderr, leaving prompts unterminated and polluting piped output; the stream routing needs correction and a split-stream regression test.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the latest commit: silent-prompt completion and cancel newlines now go through output.error() (stderr), matching where read-user-info writes prompts. Added split-stream Display regressions that assert stdout stays empty while stderr gets the terminating newline.
Locally: tap test/lib/utils/display.js read-user-info.js open-url.js auth.js (all green).
Display was terminating silent password prompts and cancelled reads with
output.standard(), which writes to stdout. When stdout is piped that left
the stderr prompt unterminated and polluted captured command output.
Route those newlines through output.error() (stderr) and add split-stream
regressions for silent completion and cancel.
Addressed the latest Copilot note on Display newlines:
Silent-prompt completion and cancel now write their terminating newline via output.error() (stderr), so piped stdout is not polluted and the stderr prompt line is closed.
Added split-stream regressions in test/lib/utils/display.js.
Locally: tap test/lib/utils/display.js read-user-info.js open-url.js auth.js.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The shared rejection handler sends cancellation newlines to stderr even for stdout prompts, leaving the visible prompt unterminated when stderr is redirected.
Display always wrote cancel newlines with output.error(), so stdout
prompts such as npm init and trust confirm lost their terminating
newline when stderr was redirected. Record the prompt stream on input
metadata, keep stdout for existing callers, and use stderr for auth
and OTP prompts. Completion and cancel newlines follow that stream.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes
Fixes #10190
What
otpleaseonly prompted for OTP when bothstdinandstdoutwere TTYs, andread-user-infousedread's defaultoutput(process.stdout). Spawning npm with piped stdout (e.g.stdio: ['inherit', 'pipe', 'inherit']) therefore failed with bareEOTPinstead of showing an interactive OTP prompt, even though the user could still see stderr and type on stdin.Changes
lib/utils/auth.js: gate interactive OTP onstdin+stderrTTY (not stdout)lib/utils/read-user-info.js: send prompts toprocess.stderrtest/lib/utils/auth.js: update TTY mocks; add regression for piped stdout + TTY stderrTesting
tap test/lib/utils/auth.js --no-coverage— pass (incl. new piped-stdout case)tap test/lib/utils/read-user-info.js --no-coverage— passnpm testnot run locally (large; relying on CI)npm trust/ OTP session in this environment; covered by unit mocks matching the reporter's TTY layoutAI disclosure
This change was prepared with AI assistance (Grok Bot / Cursor agent). I reviewed the diff, updated/ran the tests above, and believe the fix matches the issue.