Skip to content

Fix has_stdin() misfiring on non-interactive shells (/dev/null stdin)#6254

Open
Copilot wants to merge 5 commits intomainfrom
copilot/fix-utils-has-stdin-bug
Open

Fix has_stdin() misfiring on non-interactive shells (/dev/null stdin)#6254
Copilot wants to merge 5 commits intomainfrom
copilot/fix-utils-has-stdin-bug

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

Utils\has_stdin() incorrectly returns true in cron/atd/puppet environments where STDIN is connected to /dev/null. stream_select() with timeout=0 returns 1 for /dev/null because EOF is immediately readable in POSIX terms — indistinguishable from a stream with actual data.

Changes

  • php/utils.php: Prepend an fstat(STDIN) check before stream_select(). If the file type bits indicate a character device (S_IFCHR = 0020000) — which covers both TTYs and /dev/null — return false immediately. Pipes and regular files fall through to the existing stream_select() path.
// Before: stream_select() returns 1 for /dev/null (EOF = immediately readable → misfire)
wp option patch update wp_user_roles administrator capabilities unfiltered_html false < /dev/null
# Error: No data exists for key "false"

// After: fstat detects /dev/null as a character device → has_stdin() = false → args parsed correctly
  • tests/UtilsTest.php: Two subprocess tests verifying the boundary cases:
    • has_stdin() returns false when STDIN is /dev/null
    • has_stdin() returns true when data is actually piped

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php rc/Type/PHPUnit/MockForIntersectionDynamicReturnTypeExtension.php rc/Type/PHPUnit/DynamicCallToAssertionIgnoreExtension.php rc/Type/PHPUnit/MockBuilderDynamicReturnTypeExtension.php rc/Rules/PHPUnitiptables rc/Rules/PHPUnit-w rc/Rules/PHPUnit-t rc/Rules/PHPUnitsecurity rc/R�� rc/Rules/PHPUnitOUTPUT rc/Rules/PHPUnit-d (dns block)
  • nosuchhost_asdf_asdf_asdf.com
    • Triggering command: /usr/bin/php php vendor/bin/phpunit --color=always --bootstrap ./vendor/wp-cli/wp-cli-tests/tests/bootstrap.php rc/Type/PHPUnit/MockForIntersectionDynamicReturnTypeExtension.php rc/Type/PHPUnit/DynamicCallToAssertionIgnoreExtension.php rc/Type/PHPUnit/MockBuilderDynamicReturnTypeExtension.php rc/Rules/PHPUnitiptables rc/Rules/PHPUnit-w rc/Rules/PHPUnit-t rc/Rules/PHPUnitsecurity rc/R�� rc/Rules/PHPUnitOUTPUT rc/Rules/PHPUnit-d (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Helper function Utils\has_stdin() misfires depending on shell environment</issue_title>
<issue_description>Given a command like:

wp option patch update gglcptch_options login_form 0

run on a non interactive shell (like through atd/crod/puppet exec) we receive an Error:

Error: No data exists for key "0"

if we run the same program through the command line everything works fine.

Taking a look at the code here

https://github.com/wp-cli/entity-command/blob/v1.2.0/src/Option_Command.php#L525

and trying the command:

echo 1 | wp option patch update gglcptch_options login_form

gave us the expected results even in atd/crod/puppet exec shells.
</issue_description>

Comments on the Issue (you are @copilot in this section)

@schlessera At first glance, this looks like a bug in the argument parsing code.

I could reproduce it by adding < /dev/null to it:

$ wp option patch update wp_user_roles administrator capabilities unfiltered_html false
Success: Updated 'wp_user_roles' option.

$ wp option patch update wp_user_roles administrator capabilities unfiltered_html false < /dev/null
Error: No data exists for key "false"

I suppose it detects the < /dev/null as a valid STDIN input. This makes it pick STDIN as value, so the actual value we had provided gets interpreted as a key as well.</comment_new>
<comment_new>@schlessera
Some more details on this bug after trying to resolve it without success yesterday...

The root cause lies in WP_CLI\Entity\Utils::has_stdin(). Under some circumstances (due to the way the surrounding shell has called the process), the stream on STDIN is present, but returns an empty value.

This in turn will cause the above logic to miscount the arguments.

I fixed the symptom in the above code in the following way:

$stdin_value = WP_CLI\Entity\Utils::has_stdin()
	? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) )
	: null;
$patch_value = ! empty( $stdin_value )
	? WP_CLI::read_value( $stdin_value, $assoc_args )
	: WP_CLI::read_value( array_pop( $key_path ), $assoc_args );

So, for the option patch call, the problem is fixed with v2. However, I didn't yet solve the root cause in WP_CLI\Entity\Utils::has_stdin(), so places where we cannot just trick with the values are not yet fixed.

The one case I know of so far is wp user import-csv -.</comment_new>
<comment_new>@schlessera
Related wp-cli/entity-command#194 </comment_new>
<comment_new>@schlessera
@soderlind I already experimented with a similar approach, reading the mode directly, but the main issue is not detecting what type of pipe we have, but rather, whether the pipe has content or not. Did you find a way to do that through the above (mode-based) method, or did you only test for the type of pipe?</comment_new>
<comment_new>@schlessera
@soderlind The above code for reading from STDIN would be a blocking request, though. That works fine if you can rely on STDIN content being available. But if the content is not available, WP-CLI will just hang, without the user knowing what is going on...

I'll experiment some more with this, maybe it is just about finding the right combination of checking mode and reading/skipping.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…ull stdin

Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Utils\has_stdin() misfire in non-interactive shells Fix has_stdin() misfiring on non-interactive shells (/dev/null stdin) Mar 2, 2026
@swissspidy swissspidy requested a review from Copilot March 2, 2026 21:42
@swissspidy

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

This comment was marked as resolved.

swissspidy and others added 2 commits March 2, 2026 22:49
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@swissspidy swissspidy marked this pull request as ready for review March 2, 2026 21:49
@swissspidy swissspidy requested a review from a team as a code owner March 2, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Helper function Utils\has_stdin() misfires depending on shell environment

3 participants