Environment
- OS: macOS / Darwin arm64
- Node Version: v24.16.0
- Package:
@clack/prompts, @clack/core
- Package Version:
@clack/prompts@1.5.1, @clack/core@1.4.1
Describe the bug
Submitting an empty password() prompt can resolve undefined, even though the public type signature is Promise<string | symbol>.
This is surprising because the prompt result is typed as a string after isCancel(result) is checked, so code like this type-checks but can throw at runtime when the user presses Enter without typing a password:
const value = await password({ message: "Password" });
if (!isCancel(value)) {
value.trim(); // Type-checks, but can throw if value is undefined.
}
This also differs from text(), which resolves "" for an empty submit.
Looking at the current source, TextPrompt has a finalize handler that normalizes a missing value to "", while PasswordPrompt only updates its value from userInput and does not appear to perform the same empty-submit normalization.
To Reproduce
Minimal manual reproduction:
import { password, text } from "@clack/prompts";
const passwordValue = await password({ message: "Password" });
console.log("password", { type: typeof passwordValue, value: passwordValue });
const textValue = await text({ message: "Text" });
console.log("text", { type: typeof textValue, value: textValue });
Steps to reproduce the behavior:
- Run the script in a TTY.
- Press Enter at the password prompt without typing anything.
- Press Enter at the text prompt without typing anything.
Observed result:
password { type: "undefined", value: undefined }
text { type: "string", value: "" }
I also reproduced this with a fake TTY by emitting a Return keypress against the prompt input stream; the password() result was undefined while text() returned "".
Expected behavior
password() should either:
- resolve
"" on empty submit, matching text() and the current Promise<string | symbol> type, or
- include
undefined in its public return type if undefined is intentional behavior.
The first option seems more consistent with the documented behavior that the password prompt behaves like the text prompt, but masked.
Additional Information
This surfaced downstream because strict TypeScript did not warn about .trim() after isCancel() handling. The dependency type says password() returns only string | symbol, but runtime can produce undefined for empty password input.
Environment
@clack/prompts,@clack/core@clack/prompts@1.5.1,@clack/core@1.4.1Describe the bug
Submitting an empty
password()prompt can resolveundefined, even though the public type signature isPromise<string | symbol>.This is surprising because the prompt result is typed as a string after
isCancel(result)is checked, so code like this type-checks but can throw at runtime when the user presses Enter without typing a password:This also differs from
text(), which resolves""for an empty submit.Looking at the current source,
TextPrompthas afinalizehandler that normalizes a missing value to"", whilePasswordPromptonly updates its value fromuserInputand does not appear to perform the same empty-submit normalization.To Reproduce
Minimal manual reproduction:
Steps to reproduce the behavior:
Observed result:
password { type: "undefined", value: undefined } text { type: "string", value: "" }I also reproduced this with a fake TTY by emitting a Return keypress against the prompt input stream; the
password()result wasundefinedwhiletext()returned"".Expected behavior
password()should either:""on empty submit, matchingtext()and the currentPromise<string | symbol>type, orundefinedin its public return type ifundefinedis intentional behavior.The first option seems more consistent with the documented behavior that the password prompt behaves like the text prompt, but masked.
Additional Information
This surfaced downstream because strict TypeScript did not warn about
.trim()afterisCancel()handling. The dependency type sayspassword()returns onlystring | symbol, but runtime can produceundefinedfor empty password input.