Skip to content

fix(cli-internal): guard confirmPrompt against node 24 readline crash#14949

Draft
sarayev wants to merge 1 commit into
devfrom
fix/node24-confirm-prompt-readline
Draft

fix(cli-internal): guard confirmPrompt against node 24 readline crash#14949
sarayev wants to merge 1 commit into
devfrom
fix/node24-confirm-prompt-readline

Conversation

@sarayev

@sarayev sarayev commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description of changes

amplify add function crashes on Node 24 with the generic
🛑 There was an error adding the function resource. This restores the
pre-Node-24 behavior.

Root cause

Node 24 changed readline.Interface#pause() to throw ERR_USE_AFTER_CLOSE
when the interface is already closed; Node 22 and earlier treated the call as a
no-op. inquirer@7's baseUI.close() calls rl.pause() unconditionally during
prompt teardown. So when stdin closes (EOF / piped input) just as a confirm
prompt finishes, the pause() call throws, the prompt promise rejects, and the
error bubbles up and aborts the command.

The user-visible crash path is amplify-category-function's openEditor(),
which asks "Do you want to edit the local … file now?" through
context.amplify.confirmPrompt(...). That routes to the legacy
confirm-prompt.ts helper, which is built on inquirer@7 and therefore hits
the throw.

Fix

Guard the legacy confirmPrompt helper (the implementation behind
context.amplify.confirmPrompt) so an ERR_USE_AFTER_CLOSE raised during
teardown is caught. inquirer records the user's answer on the prompt UI
before teardown runs, so the helper recovers it from the prompt promise's
ui.answers and returns it — exactly what the caller would have received on
Node ≤22. If the prompt closed before any answer was recorded, it falls back to
the supplied default. Any other error is re-thrown unchanged.

Because every caller of context.amplify.confirmPrompt funnels through this one
helper, this fixes amplify add function on Node 24 and the same latent crash
for all ~28 other callers, without changing behavior on older Node versions.

Why guard instead of re-implementing on amplify-prompts

confirmPrompt is @deprecated in favor of amplify-prompts' enquirer-based
prompter, and re-pointing it there was considered. It was rejected as too
risky for a bug fix: the prompter throws in non-interactive (non-TTY) shells
and owns --yes handling, whereas several existing confirmPrompt callers
(e.g. s0-analyzeProject) already gate --yes themselves and rely on the
current non-interactive semantics. The narrow guard restores the exact
pre-Node-24 contract for every caller and is the lower-risk change. Migrating
callers to amplify-prompts remains a good future cleanup, separate from this
fix.

Relation to the e2e harness stopgap

A separate amplify-category-api change (#3503) patches the e2e test
harness's
installed inquirer as a stopgap so suites stop crashing on Node
24. This PR is the actual product fix; once it ships in a cli-internal
release, that harness patch can be retired.

Issue #, if available

N/A. Related stopgap: aws-amplify/amplify-category-api#3503 (e2e harness patch,
can be retired once this ships).

Description of how you validated changes

  • Added unit regression tests in
    packages/amplify-cli/src/__tests__/extensions/amplify-helpers/confirm-prompt.test.ts
    that simulate the Node 24 teardown crash: the answer is captured on
    ui.answers and the prompt promise then rejects with ERR_USE_AFTER_CLOSE.
    The tests assert confirmPrompt returns the real answer (yes/no) instead of
    throwing, falls back to the default when no answer was captured, and still
    re-throws unrelated errors.
  • Independently verified against the real inquirer@7 that the answer is
    present on the prompt promise's ui.answers after the simulated crash, so the
    recovery reads a real value rather than a test artifact.
  • Built with tsc -b (clean) and ran the amplify-helpers Jest suite:
    40 suites / 155 tests pass. Lint clean on both touched files.

Checklist

  • PR description included
  • yarn test passes (touched amplify-helpers suite: 40 suites / 155 tests)
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)
  • New AWS SDK calls or CloudFormation actions have been added to relevant test and service IAM policies
  • Pull request labels are added

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Node 24 changed readline.Interface#pause() to throw
ERR_USE_AFTER_CLOSE when the interface is already closed; Node 22
and earlier treated the call as a no-op. inquirer@7's
baseUI.close() calls rl.pause() unconditionally during prompt
teardown, so when stdin closes (EOF / piped input) as a confirm
prompt finishes, the throw rejects the prompt promise and bubbles
up. This is what makes `amplify add function` die with the generic
"There was an error adding the function resource" on Node 24: its
openEditor() step asks a confirm question through
context.amplify.confirmPrompt().

Guard the legacy confirmPrompt helper (the implementation behind
context.amplify.confirmPrompt) so an ERR_USE_AFTER_CLOSE thrown
during teardown is caught. The answer is captured on the prompt UI
before teardown runs, so we recover it from the prompt promise's
ui.answers and return it, restoring the pre-Node-24 behavior where
the teardown was a silent no-op. If the prompt closed before an
answer was recorded, we fall back to the supplied default.

confirmPrompt has ~28 callers. Re-implementing it on top of
amplify-prompts' enquirer-based prompter would change --yes and
non-interactive behavior for all of them (callers such as
s0-analyzeProject already gate --yes themselves), so the narrow
guard is the lower-risk fix and covers every caller, including
openEditor, at once.

Testing: added regression tests that simulate inquirer's teardown
crash (answer captured on ui.answers, then the promise rejects
with ERR_USE_AFTER_CLOSE) and assert confirmPrompt returns the
real answer instead of throwing; also verified against the real
inquirer@7 that the answer is present on the prompt promise's
ui.answers after the crash. Built with `tsc -b` and ran the
amplify-helpers jest suite (40 suites / 155 tests pass); lint
clean.

A separate amplify-category-api change patches the e2e harness's
installed inquirer as a stopgap; that patch can be retired once
this product fix ships in a cli-internal release.

---
Prompt: Root-fix the Node 24 `amplify add function` crash in the
PUBLISHED Gen1 CLI (repo: amplify-cli; PRs target the `dev`
branch). ROOT CAUSE: Node 24 made readline.Interface THROW
ERR_USE_AFTER_CLOSE when pause() is called after the readline is
closed (Node <=22 silently no-op'd). The legacy helper
confirm-prompt.ts uses inquirer@7.3.3; inquirer's baseUI.js
close() calls this.rl.pause() unconditionally. When stdin closes
(EOF / piped input) right as a confirm prompt finishes, Node 24
throws and `amplify add function` dies with the generic "There was
an error adding the function resource". The crash path is
amplify-category-function openEditor() ->
context.amplify.confirmPrompt(...). confirmPrompt is @deprecated in
favor of confirmContinue from amplify-prompts (enquirer-based),
which does not have this bug. Produce the durable fix in
amplify-cli: prefer making confirmPrompt Node-24-safe at the source
so every caller benefits, either by re-implementing on
amplify-prompts' prompter while preserving the exact public
contract, or, if that risks changing behavior for the many callers,
by guarding the inquirer path against the closed-readline crash
(the minimal change that restores pre-Node-24 behavior for all
callers). Also ensure openEditor() no longer crashes. Add a
regression test that reproduces the closed-readline /
EOF-after-confirm scenario and asserts it no longer throws. Build +
test the touched package(s). Follow the repo commit rules and open
a draft PR targeting `dev`, noting that amplify-category-api #3503's
harness patch is a stopgap that can be retired once this ships in a
cli-internal release.
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.

1 participant