gitbutlerapp/gitbutler · info

programs was just checked to be non-empty

Error message

programs was just checked to be non-empty

What it means

`but open` may find several candidate programs that can open a repo/workspace; it then builds an interactive selector via NonEmpty::from_vec and expects success. NonEmpty::from_vec returns None only for an empty vec, and the branch is entered only when !programs.is_empty() two lines earlier, so as written the panic is unreachable — it exists to document the invariant, not to handle a runtime condition.

Source

Thrown at crates/but/src/command/open.rs:154

                            .arg_value(program_id),
                    )
                })?
        }
        None => {
            let program_specs = list_program_specs_for_openable(&to_open);
            match TryInto::<[ProgramSpec; 1]>::try_into(program_specs) {
                Ok([program_spec]) => program_spec,
                Err(mut programs) => {
                    if !programs.is_empty() {
                        if let Some(mut input) = out.prepare_for_terminal_input() {
                            let options = NonEmpty::from_vec(
                                programs
                                    .iter()
                                    .enumerate()
                                    .map(|(idx, program)| (&program.id, idx))
                                    .collect::<Vec<_>>(),
                            )
                            .expect("programs was just checked to be non-empty");

                            let Some(selection) = input.prompt_select(
                                "Could not automatically choose program. Choose one to open with",
                                &options,
                            )?.copied()
                            else {
                                return Err(bad_input("No program picked").into());
                            };

                            programs.swap_remove(selection)
                        } else {
                            let program_ids =
                                programs.into_iter().map(|program| program.id).join(", ");
                            return Err(bad_input(format!(
                                "Could not automatically choose program. Found {program_ids}"
                            ))
                            .hint("Specify a program with `--program-id`")
                            .into());

View on GitHub (pinned to 2497b8007a)

Solutions

  1. If you see this panic you are on a modified build — diff crates/but/src/command/open.rs against upstream
  2. Make the invariant structural: construct the NonEmpty once and branch on Some/None so emptiness and the type agree (see exampleFix)
  3. Alternatively return a typed error (bad_input) instead of expect if the guard is ever removed
  4. Report upstream with a backtrace if an unmodified build panics

Example fix

// before — guard and conversion separated by an expect
if !programs.is_empty() {
    let options = NonEmpty::from_vec(...).expect("programs was just checked to be non-empty");
    ...
}

// after — branch on the constructor so the type enforces the check
if let Some(mut programs_ne) = NonEmpty::from_vec(std::mem::take(&mut programs)) {
    // interactive selection using programs_ne
} else {
    // empty → non-interactive fallback path
}
Defensive patterns

Strategy: validation

Validate before calling

// before invoking the open flow, validate the candidate set once
let programs: Vec<ProgramSpec> = collect_program_specs(...);
if programs.is_empty() {
    return Err(anyhow::anyhow!("no program available to open this workspace"));
}

Prevention

When it happens

Trigger: Cannot fire in the current code. Would fire only if a future refactor removed entries between the is_empty check and the conversion (e.g. filtering programs inside the branch) or NonEmpty::from_vec changed semantics.

Common situations: None for users of shipped builds. Developers encounter it while refactoring the program-selection flow in crates/but/src/command/open.rs and accidentally breaking the guard-to-conversion adjacency.

Related errors


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