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
Found while implementing #6531 (os login --json → NDJSON). Unclaimed, unassigned. Different path, different decision, same stdout-purity harm class — filed separately rather than widened into PR #6727.
Observation
#6531 covers the device flow (stdin.isTTY true). The path below it is broken in a different way. packages/cli/src/commands/login.ts, the non-TTY fallback:
readline writes the prompt to the output stream, which is process.stdout — unconditionally, including under --json. promptPassword does the same (process.stdout.write(promptText) on its own non-TTY branch).
Measured (unmodified origin/main @ b230e5efd)
$ HOME=<tmp> os login --json --url http://127.0.0.1:1 < /dev/null > out.txt 2> err.txt
exit=13
$ cat -A out.txt
Email: $ # ← no trailing newline; this is the ENTIRE stdout
So the whole of stdout under a declared machine-readable flag is the string Email: . Not a JSON document, not NDJSON, no payload at all. stderr carries only Warning: Detected unsettled top-level await.
The exit code is the second half: readline gets EOF from the closed stdin, the question promise never settles, and Node tears the process down through the unsettled-top-level-await path — exit 13, which is neither the CLI's 0 nor its 1 (CliExitCode in utils/format.ts deliberately admits only those two). A CI step judging success by exit status sees a code the CLI does not define.
Why it matters
This is the path a CI runner actually takes: no TTY, and --email/--password not passed (forgotten, or the secret failed to interpolate). The audience --json exists for gets Email: on stdout and exit 13, with nothing naming the real problem ("email and password are required in a non-interactive shell").
PR #6727 makes os login --json a declared NDJSON stream and routes every --json write through one compact emitter, so the payload side of this command is now uniform. It deliberately does not touch this path, because the fix turns on a contract question that PR cannot answer on its own:
Should os login --json interactively prompt at all? Plausible shapes, undecided:
Refuse. Under --json with no --email/--password and no TTY, emit {"success":false,"error":"…"} with exit 1 and never prompt. Treats --json as "non-interactive by definition".
Option 1 also fixes the exit-13 half; 2 and 3 leave it. Recommendation is not mine to make — this is a --json contract decision like #6531's was.
Separately and regardless of the shape chosen, the unsettled-await exit 13 is a defect on its own: an EOF on stdin should produce a defined CliExitCode, not a Node teardown artifact.
Found while implementing #6531 (
os login --json→ NDJSON). Unclaimed, unassigned. Different path, different decision, same stdout-purity harm class — filed separately rather than widened into PR #6727.Observation
#6531 covers the device flow (
stdin.isTTYtrue). The path below it is broken in a different way.packages/cli/src/commands/login.ts, the non-TTY fallback:readlinewrites the prompt to theoutputstream, which isprocess.stdout— unconditionally, including under--json.promptPassworddoes the same (process.stdout.write(promptText)on its own non-TTY branch).Measured (unmodified
origin/main@b230e5efd)So the whole of stdout under a declared machine-readable flag is the string
Email:. Not a JSON document, not NDJSON, no payload at all. stderr carries onlyWarning: Detected unsettled top-level await.The exit code is the second half:
readlinegets EOF from the closed stdin, the question promise never settles, and Node tears the process down through the unsettled-top-level-await path — exit 13, which is neither the CLI's0nor its1(CliExitCodeinutils/format.tsdeliberately admits only those two). A CI step judging success by exit status sees a code the CLI does not define.Why it matters
This is the path a CI runner actually takes: no TTY, and
--email/--passwordnot passed (forgotten, or the secret failed to interpolate). The audience--jsonexists for getsEmail:on stdout and exit 13, with nothing naming the real problem ("email and password are required in a non-interactive shell").Why it is not part of #6531 / PR #6727
PR #6727 makes
os login --jsona declared NDJSON stream and routes every--jsonwrite through one compact emitter, so the payload side of this command is now uniform. It deliberately does not touch this path, because the fix turns on a contract question that PR cannot answer on its own:Should
os login --jsoninteractively prompt at all? Plausible shapes, undecided:--jsonwith no--email/--passwordand no TTY, emit{"success":false,"error":"…"}with exit 1 and never prompt. Treats--jsonas "non-interactive by definition".os login --json(device flow) writes TWO JSON documents to stdout, so the whole stream is unparseable #6531).os migrate recorded-by --json的 stdout 里混着内核 INFO 日志,payload 无法直接 JSON.parse #6217'sreserveStdoutForJson()seam here, which forwards the prompt to stderr as a side effect. Same end state as 2, reached structurally.Option 1 also fixes the exit-13 half; 2 and 3 leave it. Recommendation is not mine to make — this is a
--jsoncontract decision like #6531's was.Separately and regardless of the shape chosen, the unsettled-await exit 13 is a defect on its own: an EOF on stdin should produce a defined
CliExitCode, not a Node teardown artifact.Refs #6531, #6217, PR #6727.