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

  1. 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.
  2. After a clap major upgrade, re-check `MatchesError` variants and keep the explicit `UnknownArgument` arm.
  3. As a user, re-install a released CLI build instead of a locally patched one.
  4. 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 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.