Summary
In the Fleet setup wizard, typing a model/provider filter that matches zero routes crashes the TUI with index out of bounds: the len is 0 but the index is 0.
Reproduce
/fleet setup (or flow "ready-made fleet + custom") → advance to the model selection step.
- Press
/ to enable the type-to-filter (model_filter_active).
- Type a query that matches nothing, e.g.
minimax followed by a space (or any non-existent provider/model).
- The wizard panics. Repeated twice in the same session.
Crash (from logs):
Process panicked at crates/tui/src/tui/views/fleet_setup.rs:2391:19:
index out of bounds: the len is 0 but the index is 0
thread 'codewhale-main' panicked at ... fleet_setup.rs:2391:19
Root cause
render_choice_step indexes the detail pane unconditionally:
// fleet_setup.rs:2391
let choice = &choices[selected.min(choices.len().saturating_sub(1))];
When choices is empty, choices.len().saturating_sub(1) is 0, selected.min(0) is 0, so choices[0] panics. The saturating_sub(1) guard does not protect against len == 0.
The Step::Model renderer builds filtered_choices from the typed query and always calls render_choice_step with it:
// fleet_setup.rs (~1876)
let filtered_choices: Vec<Choice> = filtered.iter().map(...).collect();
...
render_choice_step(chunks[1], buf, &filtered_choices, selected, &context);
With a no-match query, filtered_choices is empty and the crash occurs.
(Regression introduced by commit 48f1414e40 "tui(fleet): type to find a route instead of arrowing past all of them" — the new filter path.)
Proposed fix
Guard against an empty choices in render_choice_step — render an empty-state row (e.g. No routes match "<query>") and skip the detail pane instead of indexing. For example:
// detail: if there is nothing to show, render a "no matches" state and return
let Some(choice) = choices.get(selected.min(choices.len().saturating_sub(1))) else {
// draw the empty list / message; no detail pane
return;
};
(or equivalently early-return in the Step::Model caller when filtered_choices.is_empty()).
Acceptance criteria
- Typing a filter with no matches in the Fleet setup model step shows a "no matches" state and never panics.
- Navigating up/down with an empty filter result is safe (no index access).
- A filter that matches rows still works as before.
Environment
- codewhale
0.9.12 (dev), Linux, tmux. Reproducible on current main.
Summary
In the Fleet setup wizard, typing a model/provider filter that matches zero routes crashes the TUI with
index out of bounds: the len is 0 but the index is 0.Reproduce
/fleet setup(or flow "ready-made fleet + custom") → advance to the model selection step./to enable the type-to-filter (model_filter_active).minimaxfollowed by a space (or any non-existent provider/model).Crash (from logs):
Root cause
render_choice_stepindexes the detail pane unconditionally:When
choicesis empty,choices.len().saturating_sub(1)is0,selected.min(0)is0, sochoices[0]panics. Thesaturating_sub(1)guard does not protect againstlen == 0.The
Step::Modelrenderer buildsfiltered_choicesfrom the typed query and always callsrender_choice_stepwith it:With a no-match query,
filtered_choicesis empty and the crash occurs.(Regression introduced by commit
48f1414e40"tui(fleet): type to find a route instead of arrowing past all of them" — the new filter path.)Proposed fix
Guard against an empty
choicesinrender_choice_step— render an empty-state row (e.g.No routes match "<query>") and skip the detail pane instead of indexing. For example:(or equivalently early-return in the
Step::Modelcaller whenfiltered_choices.is_empty()).Acceptance criteria
Environment
0.9.12 (dev), Linux, tmux. Reproducible on currentmain.