googleworkspace/cli · warning · GwsError
Unexpected error reading --remove argument: {e}
Error message
Unexpected error reading --remove argument: {e} What it means
`matches.try_get_one::<String>("remove")` returned a `clap::parser::MatchesError` other than `UnknownArgument`. The code intentionally tolerates `UnknownArgument` because plain `+reply` does not define `--remove` (only `+reply-all` does); any other variant — typically `Downcast`/`WrongType` (the arg exists but was declared with a non-String value parser) or an invalid-UTF8 value — surfaces here. It is effectively an internal contract violation between the two subcommands' argument definitions.
Source
Thrown at crates/google-workspace-cli/src/helpers/gmail/reply.rs:430
</div>",
attribution, quoted_body,
)
}
// --- Argument parsing ---
fn parse_reply_args(matches: &ArgMatches) -> Result<ReplyConfig, GwsError> {
// try_get_one because +reply doesn't define --remove (only +reply-all does).
// Explicit match distinguishes "arg not defined" from unexpected errors.
let remove = match matches.try_get_one::<String>("remove") {
Ok(val) => val
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.map(|s| Mailbox::parse_list(&s))
.filter(|v| !v.is_empty()),
Err(clap::parser::MatchesError::UnknownArgument { .. }) => None,
Err(e) => {
return Err(GwsError::Other(anyhow::anyhow!(
"Unexpected error reading --remove argument: {e}"
)))
}
};
Ok(ReplyConfig {
message_id: matches.get_one::<String>("message-id").unwrap().to_string(),
body: matches.get_one::<String>("body").unwrap().to_string(),
from: parse_optional_mailboxes(matches, "from"),
extra_to: parse_optional_mailboxes(matches, "to"),
cc: parse_optional_mailboxes(matches, "cc"),
bcc: parse_optional_mailboxes(matches, "bcc"),
remove,
html: matches.get_flag("html"),
attachments: parse_attachments(matches)?,
})
}
View on GitHub (pinned to a3768d0e82)
Solutions
- If you modified the arg definitions, ensure `--remove` is declared as `Arg::new("remove").value_parser(clap::builder::StringValueParser::default())` (or plain String) in every subcommand that defines it.
- After a clap major upgrade, re-check `MatchesError` variants and keep the explicit `UnknownArgument` arm.
- As a user, re-install a released CLI build instead of a locally patched one.
- Run `cargo test` in crates/google-workspace-cli — reply arg parsing has unit coverage that catches this.
Example fix
// before: only UnknownArgument is tolerated
Err(clap::parser::MatchesError::UnknownArgument { .. }) => None,
Err(e) => return Err(GwsError::Other(anyhow::anyhow!("Unexpected error reading --remove argument: {e}"))),
// after: also tolerate Downcast by reading as the concrete declared type, or assert the invariant in debug builds
Err(clap::parser::MatchesError::UnknownArgument { .. }) => None,
Err(e) => {
debug_assert!(false, "--remove declared with non-String value parser: {e}");
None
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure every subcommand that reads --remove declares it as a plain String arg
fn assert_remove_is_string(cmd: &clap::Command) {
for sub in cmd.get_subcommands() {
if let Some(arg) = sub.get_arguments().find(|a| a.get_id() == "remove") {
debug_assert!(arg.get_value_parser().type_id() == std::any::TypeId::of::<String>(),
"--remove must be String-valued in {}", sub.get_name());
}
}
} Try / catch
// Exhaustively match clap's MatchesError so future variants surface as explicit, typed failures:
match matches.try_get_one::<String>("remove") {
Ok(val) => val,
Err(clap::parser::MatchesError::UnknownArgument { .. }) => None,
Err(clap::parser::MatchesError::Downcast { .. }) => None, // tolerate type drift deliberately
Err(e) => return Err(GwsError::Other(anyhow::anyhow!("Unexpected error reading --remove argument: {e}"))),
} Prevention
- When two subcommands share a flag name, declare identical value parsers (String) in both.
- Cover +reply and +reply-all arg parsing in unit tests so a MatchesError regression fails CI, not users.
- After bumping clap, re-read the MatchesError enum — new variants fall into the fatal arm.
When it happens
Trigger: A code change declares `--remove` in `+reply` with `value_parser` producing a non-String type (e.g. `Vec<String>` via `num_args` or a custom parser); passing non-UTF8 bytes in `--remove` on Unix; a clap version bump altering MatchesError semantics.
Common situations: Almost exclusively a maintainer/developer error when editing reply.rs argument definitions — end users supplying normal string values never trigger it; CI builds after a clap upgrade.
Related errors
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/5daeee991c64f508.
Report an issue: GitHub.