Skip to content

fix(init): stop the name prompt looping when stdin is not a TTY - #1479

Open
l2ysho wants to merge 7 commits into
masterfrom
fix/init-non-tty-loop
Open

l2ysho wants to merge 7 commits into
masterfrom
fix/init-non-tty-loop

Conversation

@l2ysho

@l2ysho l2ysho commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Note

TL;DR
apify init never terminated when stdin was not a TTY. Fixed, along with three other defects on the same path.

What was broken

apify init with no name and a non-TTY stdin never exited. Not a blocked read — a retry loop writing ~28 MB/s to stderr. Measured 152 MB in 6 s, which also makes the process hard to kill.

stdinCheckWrapper throws whenever stdin cannot be read. The loop caught that, printed it, and retried. The error is permanent, so the retry could never succeed.

What changed

Defect Before After
D1 --yes did not skip the name prompt, though interactiveNote and the apify init --yes example both promise it does infinite loop exit 0, uses the directory name
D2 The retry loop treated a permanent failure as retryable infinite loop one error, exit 1
D3 The directory confirmation turned "cannot ask" into "no" exit 0, nothing done exit 1 with a message
D4 The positional name was never validated init 'Bad Name!!' exited 0 and wrote that name, failing later on push exit 1

D2 is fixed by removing the loop, not capping it. Validation moved inside inquirer's own validate, so a typo still re-asks interactively, while a prompt that cannot be answered fails once with a message naming the escape hatch:

Error: Actor name is required. Run 'apify init <name>', or pass --yes to use the current directory name.

Two supporting changes: the directory name is now sanitized for every project type rather than only Python (my_project → my-project), and the prompt validator is shared with apify create, which had its own copy.

Behavior changes worth a reviewer's attention

  • A non-Node/Python directory without a TTY goes from exit 0 to exit 1. The old exit 0 did nothing useful, but a CI script treating 0 as success will start failing.
  • An invalid explicit name is now rejected up front. That includes two cases where the name was previously discarded anyway: an already-initialized directory, and a Scrapy project. Rejecting a name the user typed beats silently dropping it.
  • The default Actor name is sanitized for all project types, not just Python.

Tests

Three e2e tests through a new runCliBounded helper, plus four local tests.

Both layers assert on the loop itself, not on a timeout. A hang and a loop are indistinguishable by deadline and cost the full deadline either way. The helper caps combined output at 256 KB and kills on breach, so a regression fails in under a second.

execa's own maxBuffer was the obvious alternative and was measured instead of assumed: 7–20 s to trip, against 25–500 ms for the counter. In one run it blew past its own 15 s timeout and still reported timedOut: false.

Not in scope

  • The held-open-pipe variant still hangs. That happens in readStdin() at entrypoint load, before any command code runs — #1206, fixed by #1330. Verified: with the eager read stubbed out, that case exits 0 in 1.3 s with the default name.
  • #1451 (global --non-interactive flag) is adjacent but different. It covers pseudo-terminals, where a TTY exists; this is the plain non-TTY path.
  • #1354 — the non-interactive error naming --confirm/--no-confirm, which do not exist. init now has its own messages; other commands still print the misleading default.
  • validateActorName says "maximum of 30 characters" while ACTOR_NAME.MAX_LENGTH is 63. Pre-existing; the new --yes failure path surfaces it more often.

Checks

lint, format, build clean. test:local 660 passed. Local e2e 44 passed. No dependency changes, so no install-size impact. update-docs produces no diff — flags, args, description and examples are untouched.

CI's one red check is actors info --input, inherited from master. The same assertion fails on master's own E2E runs daily back to 5 October, and #1473 fixes it.

🤖 Generated with Claude Code

`apify init` with no name and a non-TTY stdin never terminated: the retry
loop around the prompt treated a permanent failure as retryable and wrote
~28 MB/s to stderr until the process was killed.

- --yes now uses the current directory name, as the help already promised.
- The name prompt validates inside inquirer, so a typo re-asks and a prompt
  that cannot be answered fails once with a message naming the escape hatch.
- The directory confirmation no longer turns "cannot ask" into "no", which
  exited 0 having done nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@l2ysho l2ysho added adhoc Ad-hoc unplanned task added during the sprint. t-builders Issues owned by the Builders team. labels Oct 8, 2026
l2ysho and others added 5 commits October 8, 2026 15:17
A directory called `my_project` or `scraper.v2` is not a legal Actor name,
so --yes failed where the help says it should succeed. The Python branch
already sanitized its package name; do the same for the directory name, and
keep the clean error for names that sanitizing cannot save.

Also deduplicate the dist paths and spawn environment shared by the two e2e
runners.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The local harness resolves the project type against the repo root, so the
"directory confirmation" test was a duplicate of the missing-name one under a
misleading name. The e2e test covers that path in a real subprocess, and now
asserts the message rather than just a non-zero exit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The positional name was never validated, so `apify init 'Bad Name!!'` exited 0
and wrote a name that only failed later, on push. Both new non-interactive
errors point the user at that argument, so it has to hold up.

Also share the prompt validator with `apify create` and track the name source
so the --yes failure quotes what it actually read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@l2ysho
l2ysho marked this pull request as ready for review October 8, 2026 17:20
@l2ysho
l2ysho requested a review from DaveHanns as a code owner October 8, 2026 17:20
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Oct 8, 2026

This branch has not been deployed

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

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-builders Issues owned by the Builders team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants