Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit f10bf8f

Browse files
committed
Keep argument parsing independent from plugin discovery
parse_args only needs the tool registry when --allowedTools is present, but it was eagerly normalizing an empty list through the plugin-backed registry. On a clean CI home this made no-arg parsing fail if bundled plugin sync surfaced a broken install, and it also left parse-related tests exposed to concurrent environment mutation. Short-circuit empty allowed-tool normalization and serialize the default-permission parse tests that depend on shared process env. Constraint: Rust CI runs unit tests in parallel inside one process, so std::env mutations are shared across tests Rejected: Keep eager registry loading for empty allowed-tools lists | unnecessary work and leaks unrelated plugin failures into basic arg parsing Confidence: high Scope-risk: narrow Reversibility: clean Directive: Any test that mutates HOME, CLAW_CONFIG_HOME, or RUSTY_CLAUDE_PERMISSION_MODE must hold env_lock while code under test reads process env Tested: cargo fmt --all --check; cargo test -p rusty-claude-cli Not-tested: Additional remote workflows beyond rust-ci
1 parent bf59abc commit f10bf8f

File tree

1 file changed

+66
-0
lines changed
  • rust/crates/rusty-claude-cli/src

1 file changed

+66
-0
lines changed

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,9 @@ fn resolve_model_alias(model: &str) -> &str {
587587
}
588588

589589
fn normalize_allowed_tools(values: &[String]) -> Result<Option<AllowedToolSet>, String> {
590+
if values.is_empty() {
591+
return Ok(None);
592+
}
590593
current_tool_registry()?.normalize_allowed_tools(values)
591594
}
592595

@@ -5318,6 +5321,7 @@ mod tests {
53185321
}
53195322
#[test]
53205323
fn defaults_to_repl_when_no_args() {
5324+
let _guard = env_lock();
53215325
assert_eq!(
53225326
parse_args(&[]).expect("args should parse"),
53235327
CliAction::Repl {
@@ -5328,6 +5332,62 @@ mod tests {
53285332
);
53295333
}
53305334

5335+
#[test]
5336+
fn defaults_to_repl_when_no_args_without_loading_plugin_registry() {
5337+
let _guard = env_lock();
5338+
let root = temp_dir();
5339+
let cwd = root.join("workspace");
5340+
let config_home = root.join("config-home");
5341+
let bundled_root = cwd.join("broken-bundled");
5342+
let plugin_root = bundled_root.join("broken-hooks");
5343+
5344+
std::fs::create_dir_all(plugin_root.join(".claude-plugin"))
5345+
.expect("broken bundled manifest dir should exist");
5346+
std::fs::create_dir_all(&config_home).expect("config home should exist");
5347+
std::fs::write(
5348+
plugin_root.join(".claude-plugin").join("plugin.json"),
5349+
r#"{
5350+
"name": "broken-hooks",
5351+
"version": "1.0.0",
5352+
"description": "broken bundled fixture",
5353+
"hooks": {
5354+
"PreToolUse": ["./hooks/pre.sh"]
5355+
}
5356+
}"#,
5357+
)
5358+
.expect("broken bundled manifest should write");
5359+
std::fs::write(
5360+
config_home.join("settings.json"),
5361+
serde_json::json!({
5362+
"plugins": {
5363+
"bundledRoot": "./broken-bundled"
5364+
}
5365+
})
5366+
.to_string(),
5367+
)
5368+
.expect("config should write");
5369+
5370+
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
5371+
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
5372+
5373+
let action = with_current_dir(&cwd, || parse_args(&[]).expect("args should parse"));
5374+
5375+
match original_config_home {
5376+
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
5377+
None => std::env::remove_var("CLAW_CONFIG_HOME"),
5378+
}
5379+
std::fs::remove_dir_all(root).expect("temp config root should clean up");
5380+
5381+
assert_eq!(
5382+
action,
5383+
CliAction::Repl {
5384+
model: DEFAULT_MODEL.to_string(),
5385+
allowed_tools: None,
5386+
permission_mode: PermissionMode::DangerFullAccess,
5387+
}
5388+
);
5389+
}
5390+
53315391
#[test]
53325392
fn default_permission_mode_uses_project_config_when_env_is_unset() {
53335393
let _guard = env_lock();
@@ -5398,6 +5458,7 @@ mod tests {
53985458

53995459
#[test]
54005460
fn parses_prompt_subcommand() {
5461+
let _guard = env_lock();
54015462
let args = vec![
54025463
"prompt".to_string(),
54035464
"hello".to_string(),
@@ -5417,6 +5478,7 @@ mod tests {
54175478

54185479
#[test]
54195480
fn parses_bare_prompt_and_json_output_flag() {
5481+
let _guard = env_lock();
54205482
let args = vec![
54215483
"--output-format=json".to_string(),
54225484
"--model".to_string(),
@@ -5438,6 +5500,7 @@ mod tests {
54385500

54395501
#[test]
54405502
fn resolves_model_aliases_in_args() {
5503+
let _guard = env_lock();
54415504
let args = vec![
54425505
"--model".to_string(),
54435506
"opus".to_string(),
@@ -5491,6 +5554,7 @@ mod tests {
54915554

54925555
#[test]
54935556
fn parses_allowed_tools_flags_with_aliases_and_lists() {
5557+
let _guard = env_lock();
54945558
let args = vec![
54955559
"--allowedTools".to_string(),
54965560
"read,glob".to_string(),
@@ -5573,6 +5637,7 @@ mod tests {
55735637

55745638
#[test]
55755639
fn parses_single_word_command_aliases_without_falling_back_to_prompt_mode() {
5640+
let _guard = env_lock();
55765641
assert_eq!(
55775642
parse_args(&["help".to_string()]).expect("help should parse"),
55785643
CliAction::Help
@@ -5603,6 +5668,7 @@ mod tests {
56035668

56045669
#[test]
56055670
fn multi_word_prompt_still_uses_shorthand_prompt_mode() {
5671+
let _guard = env_lock();
56065672
assert_eq!(
56075673
parse_args(&["help".to_string(), "me".to_string(), "debug".to_string()])
56085674
.expect("prompt shorthand should still work"),

0 commit comments

Comments
 (0)