Skip to content

fix(cli): read stdin lazily so open pipes don't hang the CLI - #1330

Open
l2ysho wants to merge 1 commit into
masterfrom
claude/apify-cli-1206-verify-37b57c
Open

fix(cli): read stdin lazily so open pipes don't hang the CLI#1330
l2ysho wants to merge 1 commit into
masterfrom
claude/apify-cli-1206-verify-37b57c

Conversation

@l2ysho

@l2ysho l2ysho commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Note

TL;DR — The CLI read stdin at startup for every command, so a pipe that stays open hung it forever. Now it only reads stdin when a command actually wants it.

Closes #1206.

_shared.ts read stdin at module scope (export const cachedStdinInput = await readStdin()), so every command blocked on stdin whether or not it uses it. #1294 fixed the socket case (a spawned child's stdio: 'pipe'), but a named pipe has no wait deadline — the read never ended and the command never even ran, so 1.8.0 still hangs with no output at all:

sleep 60 | apify --version   # 1.8.0: hangs, no output

Read stdin only where it's asked for: a - arg/flag, apify run without --input, push-data/push-items without an item arg. readStdin() memoizes, since stdin can only be drained once.

Confirmations keyed off hasData, which the eager read flipped to false after draining. Without that read it stays true, so apify <confirm-cmd> < /dev/null would try to prompt on a non-TTY — gated on isTTY instead.

Unchanged by design: commands that genuinely consume stdin still wait for the writer to close, same as cat.

Verification

New e2e case: real FIFO via mkfifo, opened r+ so it never EOFs (skipped on Windows). Fails on the pre-fix build (60s timeout), passes after.

stdin before after
open pipe, no data hang, no output exits 485 ms
open pipe, with data hang, no output exits 423 ms
closed pipe exits exits 417 ms
spawn stdio: 'pipe' (socket, #1294's case) exits exits

pnpm run test:local green (2 pre-existing python-fixture failures, fail on master too — local python env). No dependency or install-size change.

🤖 Generated with Claude Code

Every command paid for stdin: `_shared.ts` did `await readStdin()` at module
scope, so `apify --version` blocked on a stream it never reads. With a named
pipe (no wait deadline, unlike the socket a spawned child gets) the read never
ended and the command never even ran.

Read stdin only when a command asks for it — a `-` arg/flag, `apify run`
without `--input`, `push-data`/`push-items` without an item. Result is
memoized, since stdin can only be drained once.

Confirmations previously keyed off `hasData`, which the eager read flipped to
false once stdin was drained. Without that read the flag stays true, so
`apify <confirm-cmd> < /dev/null` would try to prompt on a non-TTY; gate on
`isTTY` instead.

Commands that genuinely consume stdin still wait for the writer to close, same
as `cat`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the 147th sprint - Tooling team milestone Aug 14, 2026
@github-actions github-actions Bot added t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics. labels Aug 14, 2026
@l2ysho
l2ysho requested a review from vladfrangu August 14, 2026 11:00
@l2ysho l2ysho added t-builders Issues owned by the Builders team. adhoc Ad-hoc unplanned task added during the sprint. and removed t-tooling Issues with this label are in the ownership of the tooling team. labels Aug 14, 2026
@l2ysho
l2ysho marked this pull request as ready for review August 14, 2026 11:02
@l2ysho
l2ysho requested a review from DaveHanns as a code owner August 14, 2026 11:02
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.

CLI hangs after command completes when stdin is open but not a TTY

2 participants