Skip to content

fix(core): settle the prompt when its input ends - #624

Open
paoValle wants to merge 2 commits into
bombshell-dev:mainfrom
paoValle:fix/prompt-input-end
Open

paoValle wants to merge 2 commits into
bombshell-dev:mainfrom
paoValle:fix/prompt-input-end

Conversation

@paoValle

@paoValle paoValle commented Oct 19, 2026 •

Copy link
Copy Markdown

What does this PR do?

Closes #615

When stdin ends while a prompt is waiting, or before it starts, nothing settles the prompt: prompt() resolves only on submit and cancel, so await confirm() leaves an unsettled top-level await, Node exits 13, and the cursor stays hidden. It happens with closed stdin (< /dev/null, some CI runners and agent shells) and with piped input that runs out before the last prompt.

The input is now watched for end and close, and a prompt whose input already ended before it started settles immediately (readableEnded/destroyed) because no event is left to fire. Like the abort signal, ending input cancels — there is nothing left to read. A prompt that already submitted is untouched, the listeners are removed in close(), and a validator that resolves after the prompt closed no longer emits a submit for a value nobody read.

I reproduced the failure and the fix on real streams:

  • closed stdin, node repro.mjs < /dev/null — before: Warning: Detected unsettled top-level await ..., exit code 13; after: SETTLED {"cancel":true}, exit code 0.
  • piped input that runs out, printf 'y\n' | cli — the answered prompt still submits (answer: true) and only the prompt whose input ran out cancels, so this is not a blanket "EOF cancels".
  • two prompts where stdin ended while the first one ran — the second prompt hangs on master (no end left to fire) and cancels here.

Type of change

  • Bug fix
  • Feature
  • Refactor (no behavior change)
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • I have added a changeset

pnpm test: @clack/core 174 passed (13 files), @clack/prompts 618 passed (20 files), 0 failed. pnpm run types and pnpm run lint are clean. Three tests in packages/core/test/prompts/prompt.test.ts: input ending while waiting, input that already ended before the prompt started, and the submitted value surviving an end afterwards. Both halves are pinned: with the end listener removed the first test times out, and with the readableEnded check removed the second one does.

AI-generated code disclosure

  • This PR includes AI-generated code

With stdin closed (`< /dev/null`, a CI runner, an agent shell) or a pipe
that runs out before the last prompt, `prompt()` never settled: nothing
resolved on the input ending, so `await confirm()` left an unsettled
top-level await, Node exited 13 and the cursor stayed hidden.

The input is now watched for `end`/`close`, and an input that already
ended before the prompt started settles at once. Like the abort signal,
it cancels: there is nothing left to read. A prompt that already
submitted is untouched, and a validator that resolves after the prompt
closed no longer emits a submit.
@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a467781

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@clack/core Patch
@clack/prompts Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@paoValle

Copy link
Copy Markdown
Author

Note on the red detect / detect check: it is not this branch. It fails the same way on other open PRs (#622, #621) with npm error Cannot read properties of null (reading 'edgesOut') from npm install --prefix automation, and my diff touches only packages/core/src/prompts/prompt.ts, its test file and a changeset. Everything else is green: pnpm test (@clack/core 174, @clack/prompts 618), pnpm run types, pnpm run lint.

The first version of this test destroyed the stream, which reaches the
cancel through the `close` event and therefore passed with the
`readableEnded` check removed. A stream that ended before the prompt
attached does not emit `end` again, which is the case the check exists
for: the test now builds it with a finished `Readable` and asserts
`readableEnded` first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Prompts never settle when stdin ends

1 participant