gitbutlerapp/gitbutler · error

all help topics have clap command metadata

Error message

all help topics have clap command metadata

What it means

Invariant panic while printing 'but help <topic>': the code walks the clap command tree (Args::command() -> 'help' -> subcommand named after the topic) and find_subcommand_mut returned None. Every HelpTopic variant must have a same-named clap subcommand registered under 'help'; the panic means the enum and the clap definition drifted apart - a topic added or renamed on one side only.

Source

Thrown at crates/but/src/command/help.rs:52

        Some(topic) => print_topic(out, topic),
        None => print_grouped(out),
    }
}

pub fn print_grouped(out: &mut OutputChannel) -> std::fmt::Result {
    let allow_truncation = out.format().allows_truncation();
    print_grouped_with_truncation(out, allow_truncation)
}

fn print_topic(out: &mut OutputChannel, topic: HelpTopic) -> std::fmt::Result {
    use clap::CommandFactory;
    use std::fmt::Write;

    let mut cmd = Args::command();
    let topic_command = cmd
        .find_subcommand_mut("help")
        .and_then(|help| help.find_subcommand_mut(topic.name()))
        .expect("all help topics have clap command metadata");

    let t = theme::get();
    writeln!(out, "{}", t.important.paint(topic.title()))?;
    writeln!(out)?;

    // We can't easily hook into Clap's colorize choice. It's only implemented in
    // `Command::print_long_help()` and that forces use of `std::io::Stdout`, side-stepping our
    // OutputChannel implementation.
    //
    // A full implementation here would entail using `anstream::AutoStream` along with
    // `Command::get_color()` and map that to `anstream::ColorChoice`. But just checking if the
    // output is a terminal is generally sufficient as all modern terminals support ANSI escape
    // codes, so we'll stay with this simple solution for now.
    let long_help = topic_command.render_long_help();
    if out.is_terminal() {
        writeln!(out, "{}", long_help.ansi())
    } else {
        writeln!(out, "{long_help}")

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Locate the HelpTopic enum and the clap Args definition; add the missing subcommand with a name equal to topic.name()
  2. If the topic was renamed, align both sides to the same name
  3. Add a test that enumerates every HelpTopic and asserts find_subcommand_mut succeeds for each

Example fix

// before: HelpTopic::Onboarding exists in the enum, but no matching subcommand is declared

// after: declare the subcommand so names line up with topic.name()
#[derive(clap::Subcommand)]
enum HelpSubcommands {
    Stacks,
    Onboarding, // must match HelpTopic::Onboarding.name() == "onboarding"
}
Defensive patterns

Strategy: validation

Validate before calling

// CI test: every help topic must resolve to a clap subcommand
#[test]
fn all_help_topics_have_clap_metadata() {
    use clap::CommandFactory;
    let mut cmd = Args::command();
    let help = cmd.find_subcommand_mut("help").expect("help subcommand");
    for topic in HelpTopic::all() { // adjust to the actual enumeration
        assert!(help.find_subcommand_mut(topic.name()).is_some(), "missing: {}", topic.name());
    }
}

Prevention

When it happens

Trigger: A maintainer adds a HelpTopic variant without declaring the matching 'help <name>' subcommand; a topic is renamed in the enum but not in the clap definition (or vice versa); conditional compilation drops the subcommand while the enum variant remains.

Common situations: Refactors touching help topics; running a locally built binary mid-refactor; generated topic lists drifting from hand-maintained clap registrations.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/6d52d23f50170d9b. Report an issue: GitHub.