Skip to content

[Hackathon] fix(prompt): a follow-up prompt on an already-ended stdin never resolves — setup exits 13 as 'unsettled top-level await' #639

Closed as not planned

Description

@Satianurag

Verified against (commit SHA or released version)

commit a18d37c (main HEAD)

Discord Username / User ID

sati1325

What does this improvement do?

When the CLI shows a second prompt on an input stream that has already ended — e.g. the setup target-confirmation loop (CONFIRM_TARGETS_MAX_ATTEMPTS re-prompt after an invalid answer) or agent install\'s target prompt — promptText/promptSecretnever resolves.readLineonly callsisInputEnded(input) when leftover buffered input exists (src/lib/prompt.tslines 138-147), then unconditionally attachesinput.on('end', …)at line 150. Butendalready fired, so the promise never settles. With nothing left keeping the event loop alive, Node aborts withWarning: Detected unsettled top-level await … program.parseAsync` and exits with code 13; if another handle keeps the loop alive, the prompt simply hangs forever.

Repro (real PTY): run testsprite setup (no flags) and at Install skills for … [claude]: type a typo like clade and press Ctrl-D Ctrl-D (terminal EOF). Actual: after unknown target \"clade\" the re-prompt prints, then the process dies with the unsettled-TLA warning and status 13 — no error envelope, no telemetry flush. Expected: the re-prompt notices stdin has ended and resolves '' (accepting the default, like the first prompt's onEnd does) or rejects with a proper error — exiting with a documented error code, not Node's internal unsettled-await one.

Same failure at unit level on a18d37c: const input = new PassThrough(); input.end('typo\n'); await promptText('q1', {input}); await promptText('q2', {input}) — the second call never settles (verified with a Promise.race timeout).

Real-world impact: exit 13 is documented in DOCUMENTATION.md as "Feature gated (plan entitlement or per-account flag)", so scripts and CI wrappers misread this crash as a billing/entitlement failure. A typing mistake + Ctrl-D — a normal terminal habit — silently kills interactive setup.

Details / implementation notes

Root cause: readLine (src/lib/prompt.ts) checks isInputEnded(input) only inside the pending !== undefined branch (lines 138-147). With no buffered leftovers it falls through and attaches on('end', onEnd) to a stream whose readableEnded is already true — the event already fired and will never fire again, so the promise stays pending. Once stdin's EOF stops holding the event loop open, Node's unsettled-top-level-await path exits with code 13 and prints the internal warning.

Fix: after processing any pending text, check isInputEnded(input) (or readableEnded) unconditionally before attaching listeners, and finish() when ended — resolving with whatever was typed ('' if nothing), matching the first prompt's onEnd semantics. Optionally track ended streams (e.g. a WeakSet marked in onEnd) so later prompts on the same stream resolve immediately. Regression test: input.end('typo\n'); await promptText(); await promptText() must resolve '' rather than hang. The same helper backs agent install's target prompt, so the fix covers both interactive paths.

A PR with the fix + regression test will follow referencing this issue.

Confirmations

  • I have searched existing issues and this is not a duplicate.
  • I have provided my Discord identity above for reward coordination.

Activity

  1. fyZhang66 commented on Oct 22, 2026

    @fyZhang66
    Contributor

    Thanks @Satianurag. It takes a typo followed by Ctrl-D at an interactive prompt, and scripted runs pass flags instead of answering prompts, so no one has hit this in practice. Closing as not planned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    hackathonCLI hackathon submissions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions