Skip to content

wp user update silently ignores typo'ed --user_pass flag #5286

Open

Description

@rklrkl

With WP-CLI 2.3.0, I accidentally typo'ed the --user_pass param to "wp user update" as --user-pass (i.e. a minus instead of an underscore). Here's what was output:

wp user update myuser --user-pass=mypass
Success: Updated user 1.

However, it didn't set "mypass" as the user password (it left it as the old one) and didn't report that --user-pass isn't a valid flag. When I used the correct --user_pass flag, I got the same success message but it did change the password to mypass. It needs to give an error message about --user-pass being a bad flag instead of a success message. I didn't check if this applies to similar typos with other user update flags (many of them have underscores).

Activity

  1. salcode commented on Oct 6, 2019

    @salcode

    The wp user update documentation says the command accepts a number of parameters including the generic

    --<field>=<value>
    

    which is described as

    One or more fields to update. For accepted fields, see wp_update_user().

    The wp_update_user() documentation says

    See also: wp_insert_user(): For what fields can be set in $userdata.

    As far as I can tell this is an exhaustive list of the field values that can be used.

    • user_pass
    • user_nicename
    • user_url
    • user_email
    • display_name
    • nickname
    • first_name
    • last_name
    • description
    • rich_editing
    • user_registered
    • role
    • syntax_highlighting
    • comment_shortcuts
    • admin_color
    • use_ssl
    • show_admin_bar_front
    • locale
  2. salcode commented on Oct 6, 2019

    @salcode

    This same behavior occurs with other update commands,

    e.g.

    The correct version

    wp post update 1 --post_title="New title"
    

    and a version with a typo (underscore _ is replaced with a dash -)

    wp post update 1 --post-title="New title"
    

    or a version with any invalid argument.

    It would be nice to flag bad fields, perhaps with a warning.

    wp post update 1 --notafield="New title"
    

    All display

    Success: Updated post 1.

  3. schlessera commented on Feb 5, 2020

    @schlessera
    Member

    Yes, I agree, WP-CLI should verify the flag against a whitelist. This would need to be kept in sync with the WordPress Core version, though, and we need to deal with the changes between Core versions as well.

    I'm thinking about an external list of entities and their properties, with starting and/or ending version numbers for each. Having that outside of the individual commands would make it easier to maintain it and check it against new releases.

  4. dev-alamin commented on Apr 29, 2026

    @dev-alamin

    I ran into this while testing multiple entity update commands and wanted to add some additional context.

    This behavior is not limited to wp user update. It is inconsistent across entity commands:

    • wp term update rejects unknown parameters:

    Error: Parameter errors:
    unknown --foo parameter

    • wp user update, wp post update, wp comment update silently accept unknown parameters and still report success:

    Success: Updated user/post/comment

    This inconsistency makes the issue more problematic in practice, as it can lead to silent failures in automation (e.g. typos like --user_pas).

    It seems the difference comes from implementation:

    • some commands validate against a defined schema
    • others pass $assoc_args directly to core functions (wp_update_user, wp_update_post, etc.)

    Happy to help test or contribute if there's direction toward standardizing validation across commands.

  5. ikamal7 commented on Jul 23, 2026

    @ikamal7

    Following up on this issue — I opened a PR (wp-cli/entity-command#624) with an initial approach: warn on unknown parameters that look like typos of known user fields, while still allowing custom fields through for BC.

    @swissspidy suggested continuing the discussion here before settling on a direction. From the previous threads, I see a few approaches:

    A) Framework-level entity field registry (schlessera's original suggestion) — An external list of entities and their properties with version ranges, shared across commands. Most robust, but heaviest to implement.

    B) Remove --<field>=<value> + complete docblock (mrsdizzie's suggestion) — List all valid fields explicitly in each command's synopsis. This gives free validation from WP-CLI's SynopsisValidator, but breaks custom meta fields passed via CLI.

    C) Typo detection within each command (what entity-command#624 implements) — Keep --<field>=<value> for BC, but use get_suggestion() to warn on close matches. Custom fields still work, but it's per-command and doesn't scale elegantly.

    D) Something else? — Typo detection at the framework level (SynopsisValidator) even when --<field>=<value> is present? Or a hybrid?

    I'm happy to pivot the PR to whichever approach the team thinks is best. Thoughts?

  6. swissspidy commented on Jul 25, 2026

    @swissspidy
    Member

    This is one of the issues that look easy on the surface but become quite complex as soon as you look more closely.

    The discussion in wp-cli/entity-command#528 has some very good context.

    (B) offers great user experience, but kinda requires something like (A) for more accuracy. But still unclear how that would work if a plugin introduces custom arguments to wp_insert_user (or wp_insert_post or so)

    (C) is not super elegant because it reinvents the wheel. It would be great to be able to reuse what the framework already provides.

    And I guess that's something that's not very clear yet for (A):

    I think it would be relatively easy to do something like https://github.com/johnbillion/wp-compat but for wp_insert_user arguments (and others). Then this list could be bundled with the command and/or fetched dynamically from GitHub (like we do in i18n-command or https://github.com/wp-cli/wp-cli-tests/blob/artifacts/wp-versions.json), so it's kept up to date with WP releases.

    But then the command needs a way to provide this list to SynopsisValidator, and I'm not sure if we have the necessary hooks in place to support this at the moment.

    And even then, we need to figure out what to do for custom arguments if supported on the site. We don't want to break these.

  7. ekamran commented on Aug 11, 2026

    @ekamran
    Contributor

    A small case study on this issue

    I spent some time studying this issue along with wp-cli/entity-command#528 and wp-cli/entity-command#624 before attempting anything, and I would like to share my findings. Tested on WP-CLI 2.12.0 with WordPress 7.0.3 on a clean install.

    1. Current behaviour is inconsistent for the same mistake

    Command Typo input Result
    user update --user-pass=x Success, password unchanged
    post update --post-title=x Success, title unchanged
    comment update --comment-content=x Warning: Could not update comment., exit 1
    term update, user create --descriptionn=x proper error with Did you mean

    2. Plugins really do send custom fields through the catch-all

    This was an open question in the earlier discussions, so I tested it:

    • With a small plugin registering a telegram contact method (user_contactmethods filter), wp user update 1 --telegram=@handle writes the usermeta today.
    • Core also provides the insert_custom_user_meta filter (since 5.9), where a plugin can inspect the raw $userdata and add entries to the custom meta that core persists. Those keys may be known only to that plugin.

    So removing --<field>=<value> or relying only on a fixed core list could break working integrations. A core-generated list alone cannot cover fields introduced dynamically through filters.

    3. Where the behaviour comes from

    • SynopsisValidator::unknown_assoc() returns early when the synopsis contains a --<field>=<value> token, so the existing Did you mean machinery in Subcommand::validate_args() never runs for these commands.
    • The affected catch-all write commands are currently five: user update, post create, post update, comment create, comment update.
    • user create and term update already get strict validation.
    • List commands use the same token for query filters where arbitrary keys are by design, so any change should stay away from those.
    • The documented field lists are currently incomplete, comment update documents no named fields at all. Completing the docblocks would be a needed first step for any approach.

    A possible middle path, for discussion only

    Keep the catch-all, but fail only when an unknown argument is a close match of a recognised argument (edit distance 2, the existing Utils\get_suggestion()). Everything else passes exactly as today.

    The intended behaviour would be:

    $ wp user update 1 --user-pass=secret
    Error: unknown --user-pass parameter
    Did you mean '--user_pass'?
    
    $ wp user update 1 --some_plugin_key=value
    Success: Updated user 1.
    
    • Recognised set: documented core fields, command flags like --skip-email, and for users also the contact methods registered on the current site at runtime.
    • The check runs before any field is processed, so a typo never causes a partial update.
    • Far-unknown plugin fields retain current behaviour, as in the second example above.
    • Prototyped locally: --user-pass, --first-name, --descriptionn and even --telegramm get caught with the right suggestion, while --custom_field_xyz and --some_plugin_key pass through untouched.

    Honest limitations:

    • A genuine plugin field could still sit within edit distance 2 of a core field (for example user_pass2). Small collision risk remains, and would need an escape through configuration or an allowlist rather than a new CLI flag.
    • Post and comment would need complete candidate lists. These change infrequently, but staleness is not completely harmless, since a newly introduced valid field could theoretically look like a close match to an existing one.
    • A far-off typo would still pass silently. That part stays a deliberate trade-off.

    So this addresses the recorded concerns around plugin compatibility, partial updates and warning-plus-success behaviour, while candidate discovery and far-unknown handling remain open for discussion.

    One lower-risk way to evaluate this

    If the team would find it useful, I can build this as an opt-in standalone package first, similar to how I built the http proxy package earlier.

    • The before_run_command hook receives the command arguments directly and, for normal WordPress commands, runs after WordPress has loaded, so runtime contact methods can be included without modifying entity-command or the framework.
    • Limited to the five catch-all write commands above, with an escape mechanism included.
    • Opt-in installation limits most of the compatibility risk, because users knowingly choose the behaviour.
    • If it proves useful on real sites, the logic can later move into the framework or entity-command in whatever shape maintainers prefer. If not, nothing upstream has changed.

    I understand from wp-cli/entity-command#528 that the team prefers to handle this holistically rather than through piecemeal PRs, so I am not opening anything without direction. Sharing this as study material. Happy to help with implementation and Behat coverage if any part seems worth exploring, and equally happy to be corrected if I have missed something.

  8. swissspidy commented on Aug 12, 2026

    @swissspidy
    Member

    Thanks for sharing this helpful summary!

    Yes, detecting typos for the "default" arguments but passing through legit unknown ones would be the desired middle ground. This would live in Subcommand and SynopsisValidator`

    I'm still on the fence about the registry approach. It's not like these entities change that often. A static list + some unit tests might be good enough. Nothing would break anyway. If core introduces a new foobar field that we haven't documented yet, you could still use it. That would simplify things a lot.

    Appreciate the offer with the package, but I think in this case it might be easier to explore directly in the framework, as I don't think we have great hooks for this. before_run_command fires before find_command_to_run, so the package has to resolve the command itself and the runner then resolves it a second time.

  9. ekamran commented on Aug 12, 2026

    @ekamran
    Contributor

    Thanks, that makes sense.

    Agreed on keeping this in the framework, in Subcommand and SynopsisValidator, rather than only in entity-command. A static list of documented fields per entity, with unit tests, sounds like the right level. Since a new undocumented core field would still pass through, that keeps the compatibility risk low between releases and there is no registry to maintain.

    Good point on before_run_command as well. It fires before find_command_to_run, so a package there would resolve the command itself and then the runner resolves it again. Doing this natively, after the synopsis is already resolved, avoids that, so I will drop the package idea for this one.

    I will explore it directly in the framework and work it through locally first, paying attention to the tricky cases: a legit unknown plugin field that must still pass, a close typo that should error, and a mix of a valid field plus a typo where nothing should be partially updated. I can share the local findings here first before opening anything.

  10. ekamran commented on Aug 12, 2026

    @ekamran
    Contributor

    Thanks, I tried the framework direction locally and wanted to share the findings before opening anything.

    The prototype keeps the current catch-all behavior, so unknown custom/plugin fields still pass. It only errors when an unknown argument looks like a typo of a documented argument, and only for commands that opt in.

    What worked locally:

    • --user-pass is caught and suggests --user_pass.
    • --post-title is caught and suggests --post_title.
    • --comment-content is caught and suggests --comment_content.
    • A valid field plus a typo stops before the command runs, so there is no partial update.
    • Unknown custom fields still pass through.
    • List/filter commands stay unchanged because the behavior is opt-in.

    The prototype is split into two parts:

    • framework: Subcommand / SynopsisValidator support, behind an opt-in docblock tag (@detect-arg-typos, name open to change)
    • entity-command: add the tag and complete the documented fields for the five affected write commands: user update, post create, post update, comment create, comment update

    I also found one related parser issue while testing comment fields: mixed-case argument names like comment_author_IP were not parsed fully. The framework prototype fixes that too.

    A few limits are intentional:

    • It does not try to catch every possible typo.
    • Far-off typos still pass.
    • Very short names are avoided to reduce false positives.
    • user application-password update is not included for now because its main field is name, and the matcher cannot safely help there.
    • If a real plugin field ever collides with the matcher, a config-level escape can be added as a follow-up.

    Local checks are passing for the focused tests, static checks, and the new Behat coverage. Full PHPUnit depends on network availability here because unrelated HTTP tests hit example.com.

    What do you think about this direction?

  11. swissspidy commented on Aug 13, 2026

    @swissspidy
    Member

    behind an opt-in docblock tag

    I'm not keen on following an opt-in docblock approach. Ideally we find a good default that works out of the box.

    entity-command: add the tag and complete the documented fields for the five affected write commands:

    We should consider covering read commands as well, e.g. wp post list.

    I also found one related parser issue while testing comment fields: mixed-case argument names like comment_author_IP were not parsed fully

    Can you elaborate on that? If there's a bug somewhere we should address it.

  12. ekamran commented on Aug 13, 2026

    @ekamran
    Contributor

    Good points, thanks. I reworked the prototype based on this and tested again.

    I dropped the opt-in tag. The check now runs by default for commands that have --<field>=<value> together with documented parameters. What keeps this safer as a default is that it only flags unknown arguments when they are close matches to documented parameters. Far unknown fields still pass through, so plugin fields and query filters keep the current behavior unless they look like a typo of a documented argument.

    That also covers read commands. I tested against the current user list, post list, and comment list docblocks. Typos like --formt and --feilds are caught and suggest --format and --fields, while arguments like --meta_key, --orderby, --post_status, --roles, --include and other query arguments pass through untouched.

    I also ran the user, post, and comment Behat suites with the reworked framework. The only failures were the same known environment-related ones, with no new failures from this change.

    On the parser issue: in SynopsisParser::classify_token(), the parameter name pattern is lowercase only: ([a-z-_0-9]+). A docblock parameter with an uppercase letter is silently truncated at the first uppercase character. For example, [--comment_author_IP=<comment_author_IP>] gets registered as comment_author_.

    That means help output and validation do not see the real documented argument name. The fix is small: allow uppercase characters in the parameter name pattern, with a unit test. This is independent of the typo detection, so it can also be handled separately if that is easier to review.

    What do you think about this default-on direction?

  13. swissspidy commented on Aug 13, 2026

    @swissspidy
    Member

    Mind opening a PR with that comment_author_IP fix? That would be helpful. Thanks!

  14. swissspidy commented on Aug 18, 2026

    @swissspidy
    Member
  15. ekamran commented on Aug 18, 2026

    @ekamran
    Contributor

    Small update: I tested the current framework PR with the latest entity-command changes and shared one finding there.

    Happy to help with more testing or edge-case checks if useful. This has turned into quite a rabbit hole, so I am happy to be a second pair of eyes while the direction settles.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions