You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
wp user update silently ignores typo'ed --user_pass flag #5286
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).
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.
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?
(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):
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.
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.
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.
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.
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.
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?
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.
Activity
salcode commentedon Oct 6, 2019
The wp user update documentation says the command accepts a number of parameters including the generic
which is described as
The
wp_update_user()documentation saysAs far as I can tell this is an exhaustive list of the field values that can be used.
user_passuser_nicenameuser_urluser_emaildisplay_namenicknamefirst_namelast_namedescriptionrich_editinguser_registeredrolesyntax_highlightingcomment_shortcutsadmin_coloruse_sslshow_admin_bar_frontlocalesalcode commentedon Oct 6, 2019
This same behavior occurs with other update commands,
e.g.
The correct version
and a version with a typo (underscore
_is replaced with a dash-)or a version with any invalid argument.
It would be nice to flag bad fields, perhaps with a warning.
All display
schlessera commentedon Feb 5, 2020
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.
dev-alamin commentedon Apr 29, 2026
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 updaterejects unknown parameters:Error: Parameter errors:
unknown --foo parameter
wp user update,wp post update,wp comment updatesilently 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:
$assoc_argsdirectly 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.
ikamal7 commentedon Jul 23, 2026
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 useget_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?
swissspidy commentedon Jul 25, 2026
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(orwp_insert_postor 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_userarguments (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.
ekamran commentedon Aug 11, 2026
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
user update--user-pass=xSuccess, password unchangedpost update--post-title=xSuccess, title unchangedcomment update--comment-content=xWarning: Could not update comment., exit 1term update,user create--descriptionn=xDid you mean2. Plugins really do send custom fields through the catch-all
This was an open question in the earlier discussions, so I tested it:
telegramcontact method (user_contactmethodsfilter),wp user update 1 --telegram=@handlewrites the usermeta today.insert_custom_user_metafilter (since 5.9), where a plugin can inspect the raw$userdataand 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 existingDid you meanmachinery inSubcommand::validate_args()never runs for these commands.user update,post create,post update,comment create,comment update.user createandterm updatealready get strict validation.comment updatedocuments 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:
--skip-email, and for users also the contact methods registered on the current site at runtime.--user-pass,--first-name,--descriptionnand even--telegrammget caught with the right suggestion, while--custom_field_xyzand--some_plugin_keypass through untouched.Honest limitations:
user_pass2). Small collision risk remains, and would need an escape through configuration or an allowlist rather than a new CLI flag.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.
before_run_commandhook 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.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.
swissspidy commentedon Aug 12, 2026
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 andSynopsisValidator`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
foobarfield 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_commandfires beforefind_command_to_run, so the package has to resolve the command itself and the runner then resolves it a second time.ekamran commentedon Aug 12, 2026
Thanks, that makes sense.
Agreed on keeping this in the framework, in
SubcommandandSynopsisValidator, rather than only inentity-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_commandas well. It fires beforefind_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.
ekamran commentedon Aug 12, 2026
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-passis caught and suggests--user_pass.--post-titleis caught and suggests--post_title.--comment-contentis caught and suggests--comment_content.The prototype is split into two parts:
Subcommand/SynopsisValidatorsupport, behind an opt-in docblock tag (@detect-arg-typos, name open to change)user update,post create,post update,comment create,comment updateI also found one related parser issue while testing comment fields: mixed-case argument names like
comment_author_IPwere not parsed fully. The framework prototype fixes that too.A few limits are intentional:
user application-password updateis not included for now because its main field isname, and the matcher cannot safely help there.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?
swissspidy commentedon Aug 13, 2026
I'm not keen on following an opt-in docblock approach. Ideally we find a good default that works out of the box.
We should consider covering read commands as well, e.g.
wp post list.Can you elaborate on that? If there's a bug somewhere we should address it.
ekamran commentedon Aug 13, 2026
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, andcomment listdocblocks. Typos like--formtand--feildsare caught and suggest--formatand--fields, while arguments like--meta_key,--orderby,--post_status,--roles,--includeand 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 ascomment_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?
swissspidy commentedon Aug 13, 2026
Mind opening a PR with that comment_author_IP fix? That would be helpful. Thanks!
Cover the catch-all parameter check with Behat scenarios
swissspidy commentedon Aug 18, 2026
While we're fine-tuning the approach for this, I started documenting the various fields in entity-command:
wp site listentity-command#639wp site listaccepts entity-command#642wp post listaccepts entity-command#643Quite a rabbit hole, as it exposes some bugs and limitations that have been around for a while.
I had Claude explore the typo fix itself in #6392
ekamran commentedon Aug 18, 2026
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.
Demote the catch-all typo check to a warning and narrow its candidates