mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-30 03:35:20 +08:00
Avoid duplicate config warnings for JSON consumers (#3190)
JSON config output already carries collected config diagnostics in warnings[], so prose stderr emission must be reserved for text/local paths. Lazy permission-mode default resolution prevents an earlier config load from leaking the same deprecation before the JSON renderer runs.\n\nConstraint: ROADMAP #815 requires text mode to keep human stderr warnings while JSON config/list suppresses duplicate app-level config prose.\nRejected: Filtering all stderr in JSON mode | would hide cargo/compiler or unrelated diagnostics outside the app config warning path.\nConfidence: high\nScope-risk: narrow\nDirective: Keep load_collecting_warnings side-effect-free; use load() for human stderr emission.\nTested: cargo fmt; cargo test -p rusty-claude-cli --test output_format_contract config_json_reports_deprecations_structurally_without_stderr_duplicate_815; cargo test -p rusty-claude-cli --test output_format_contract; manual target/debug/claw JSON config fixture.\nNot-tested: cargo clippy -p rusty-claude-cli --all-targets -- -D warnings is blocked by pre-existing runtime dead_code/trident warnings.
This commit is contained in:
parent
c3e7b6af60
commit
89e7f415a9
@ -379,12 +379,6 @@ impl ConfigLoader {
|
|||||||
loaded_entries.push(entry);
|
loaded_entries.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still emit to stderr for non-JSON callers that go through the normal load() path;
|
|
||||||
// here we just *also* return them so callers can surface them structurally.
|
|
||||||
for warning in &all_warnings {
|
|
||||||
emit_config_warning_once(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
let merged_value = JsonValue::Object(merged.clone());
|
let merged_value = JsonValue::Object(merged.clone());
|
||||||
|
|
||||||
let feature_config = RuntimeFeatureConfig {
|
let feature_config = RuntimeFeatureConfig {
|
||||||
|
|||||||
@ -1157,7 +1157,11 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
let permission_mode = permission_mode_override.unwrap_or_else(default_permission_mode);
|
// Keep config-backed defaults lazy so pure-local JSON surfaces (notably
|
||||||
|
// `claw --output-format json config`) can report config warnings
|
||||||
|
// structurally without an earlier default-resolution load writing prose
|
||||||
|
// warnings to stderr.
|
||||||
|
let permission_mode = || permission_mode_override.unwrap_or_else(default_permission_mode);
|
||||||
|
|
||||||
match rest[0].as_str() {
|
match rest[0].as_str() {
|
||||||
"dump-manifests" => parse_dump_manifests_args(&rest[1..], output_format),
|
"dump-manifests" => parse_dump_manifests_args(&rest[1..], output_format),
|
||||||
@ -1301,7 +1305,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
model,
|
model,
|
||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode: permission_mode(),
|
||||||
compact,
|
compact,
|
||||||
base_commit,
|
base_commit,
|
||||||
reasoning_effort: reasoning_effort.clone(),
|
reasoning_effort: reasoning_effort.clone(),
|
||||||
@ -1338,7 +1342,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
model,
|
model,
|
||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode: permission_mode(),
|
||||||
compact,
|
compact,
|
||||||
base_commit: base_commit.clone(),
|
base_commit: base_commit.clone(),
|
||||||
reasoning_effort: reasoning_effort.clone(),
|
reasoning_effort: reasoning_effort.clone(),
|
||||||
@ -1350,7 +1354,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
model,
|
model,
|
||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode(),
|
||||||
compact,
|
compact,
|
||||||
base_commit,
|
base_commit,
|
||||||
reasoning_effort,
|
reasoning_effort,
|
||||||
@ -1389,7 +1393,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
model,
|
model,
|
||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode: permission_mode(),
|
||||||
compact,
|
compact,
|
||||||
base_commit,
|
base_commit,
|
||||||
reasoning_effort: reasoning_effort.clone(),
|
reasoning_effort: reasoning_effort.clone(),
|
||||||
|
|||||||
@ -1259,6 +1259,66 @@ fn inventory_commands_deduplicate_config_deprecation_warnings_per_process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_json_reports_deprecations_structurally_without_stderr_duplicate_815() {
|
||||||
|
let root = unique_temp_dir("config-json-warning-815");
|
||||||
|
let config_home = root.join("config-home");
|
||||||
|
let home = root.join("home");
|
||||||
|
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||||
|
fs::create_dir_all(&home).expect("home should exist");
|
||||||
|
fs::write(
|
||||||
|
config_home.join("settings.json"),
|
||||||
|
r#"{"enabledPlugins": {}}"#,
|
||||||
|
)
|
||||||
|
.expect("deprecated config fixture should write");
|
||||||
|
|
||||||
|
let envs = [
|
||||||
|
(
|
||||||
|
"CLAW_CONFIG_HOME",
|
||||||
|
config_home.to_str().expect("utf8 config home"),
|
||||||
|
),
|
||||||
|
("HOME", home.to_str().expect("utf8 home")),
|
||||||
|
];
|
||||||
|
let output = run_claw(&root, &["--output-format", "json", "config"], &envs);
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"stdout:\n{}\n\nstderr:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
let parsed: Value =
|
||||||
|
serde_json::from_slice(&output.stdout).expect("stdout should be valid json");
|
||||||
|
let warnings = parsed["warnings"]
|
||||||
|
.as_array()
|
||||||
|
.expect("config JSON should include warnings[]");
|
||||||
|
assert!(
|
||||||
|
warnings.iter().any(|warning| warning
|
||||||
|
.as_str()
|
||||||
|
.is_some_and(|text| text.contains("field \"enabledPlugins\" is deprecated"))),
|
||||||
|
"config JSON warnings[] should include enabledPlugins deprecation: {parsed}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let stderr = String::from_utf8(output.stderr).expect("stderr utf8");
|
||||||
|
assert!(
|
||||||
|
!stderr.contains("field \"enabledPlugins\" is deprecated"),
|
||||||
|
"JSON config should not duplicate collected config deprecations on stderr:\n{stderr}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let text_output = run_claw(&root, &["config"], &envs);
|
||||||
|
assert!(
|
||||||
|
text_output.status.success(),
|
||||||
|
"stdout:\n{}\n\nstderr:\n{}",
|
||||||
|
String::from_utf8_lossy(&text_output.stdout),
|
||||||
|
String::from_utf8_lossy(&text_output.stderr)
|
||||||
|
);
|
||||||
|
let text_stderr = String::from_utf8(text_output.stderr).expect("stderr utf8");
|
||||||
|
assert!(
|
||||||
|
text_stderr.contains("field \"enabledPlugins\" is deprecated"),
|
||||||
|
"text config should keep human-readable config warnings on stderr"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
||||||
assert_json_command_with_env(current_dir, args, &[])
|
assert_json_command_with_env(current_dir, args, &[])
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user