Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ui::{self, ctrlc};
use crate::{Result, backend};
use crate::{cli::args::ToolArg, path::PathExt};
use crate::{hook_env as hook_env_module, logger, migrate, shims};
use clap::{ArgAction, CommandFactory, Parser, Subcommand};
use clap::{ArgAction, CommandFactory, Subcommand};
use eyre::bail;
use std::path::PathBuf;

Expand Down Expand Up @@ -473,8 +473,8 @@ fn uses_deprecated_backends_alias(cmd: &clap::Command, args: &[String]) -> bool
)
}

fn warn_deprecated_backends_alias(cmd: &clap::Command, args: &[String]) {
if uses_deprecated_backends_alias(cmd, args) {
fn warn_deprecated_backends_alias(uses_alias: bool) {
if uses_alias {
deprecated_at!(
"2026.4.0",
"2027.4.0",
Expand Down Expand Up @@ -656,13 +656,20 @@ impl Cli {
});
}
// Pre-process args to handle naked runs before clap parsing
let cmd = Cli::command();
let cmd = measure!("build_cli_command", { Cli::command() });
let processed_args = preprocess_args_for_naked_run(&cmd, args);
// Escape flags after task names so they go to tasks, not mise
let processed_args = escape_task_args(&cmd, &processed_args);
let deprecated_backends_alias = uses_deprecated_backends_alias(&cmd, args);

// Reuse the already-built clap command rather than letting
// `Cli::parse_from` construct the whole tree a second time.
let cli = measure!("get_matches_from", {
Cli::parse_from(processed_args.iter())
let matches = cmd.get_matches_from(processed_args.iter());
match <Cli as clap::FromArgMatches>::from_arg_matches(&matches) {
Ok(cli) => cli,
Err(err) => err.format(&mut Cli::command()).exit(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Third command build on error path

cmd.get_matches_from() calls process::exit() internally on any parse error, so from_arg_matches is only reached when matching succeeded — making the Err branch unreachable in practice. The fallback to err.format(&mut Cli::command()) therefore rebuilds the command tree a third time only on what is effectively a dead-code path (a clap internal bug or derive-macro inconsistency). This doesn't regress startup performance, but if clap ever exposes a way to format the error without a fresh command, or if try_get_matches_from is used instead, the extra build could be avoided entirely.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

}
});
// Validate --cd path BEFORE Settings processes it and changes the directory
validate_cd_path(&cli.cd)?;
Expand All @@ -673,7 +680,7 @@ impl Cli {
measure!("registry::refresh", { crate::registry::refresh().await });
let _ = measure!("backend::load_tools", { backend::load_tools().await });
}
warn_deprecated_backends_alias(&cmd, args);
warn_deprecated_backends_alias(deprecated_backends_alias);
measure!("migrate", { migrate::run().await });
if let Err(err) = crate::cache::auto_prune() {
warn!("auto_prune failed: {err:?}");
Expand Down
6 changes: 1 addition & 5 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,11 +1378,7 @@ pub fn load_config_paths(config_filenames: &[String], include_ignored: bool) ->
if config_dir_is_ignored(dir, include_ignored) {
vec![]
} else {
config_filenames
.iter()
.rev()
.flat_map(|f| glob(dir, f).unwrap_or_default().into_iter().rev())
.collect()
config_paths_in_dir_with_filenames(dir, config_filenames)
}
})
.collect::<Vec<_>>();
Expand Down
6 changes: 6 additions & 0 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,10 @@ impl Settings {
CLI_SETTINGS.lock().unwrap().clone().unwrap_or_default(),
))
.env();
time!("try_get builder1+env");

let mut settings = sb.load()?;
time!("try_get load1");
if let Some(mut cd) = settings.cd {
static ORIG_PATH: Lazy<std::io::Result<PathBuf>> = Lazy::new(env::current_dir);
if cd.is_relative() {
Expand All @@ -492,12 +494,16 @@ impl Settings {
CLI_SETTINGS.lock().unwrap().clone().unwrap_or_default(),
))
.env();
time!("try_get builder2+env");
for file in Self::all_settings_files() {
sb = sb.preloaded(file);
}
time!("try_get all_settings_files");
sb = sb.preloaded(DEFAULT_SETTINGS.clone());
time!("try_get default_settings");

settings = sb.load()?;
time!("try_get load2");
if !settings.legacy_version_file {
settings.idiomatic_version_file = Some(false);
}
Expand Down