From 7aa73fad509631497e92f1fedbcacb9f823551dc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 3 Jun 2026 01:16:19 +0000 Subject: [PATCH] fix(config): warn when shell/sandbox keys are nested under unknown sections (#2589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell tools (`exec_shell`, `task_shell_start`, …) only register when `allow_shell` is true, but `allow_shell` and `sandbox_mode` are top-level keys. Placing them under a `[general]` or `[sandbox]` table — neither of which CodeWhale defines — makes TOML silently drop them, so `allow_shell` stays false and the tools vanish from the catalog with no explanation. Following the existing `warn_on_misplaced_root_base_url` precedent, emit a startup warning naming the misplaced keys and telling the user to move them to the top of the file. With the keys correctly placed, shell tools register on Windows too (no sandbox required for danger-full-access). https://claude.ai/code/session_01MQrnh6wHfrEYN5BBdMarC1 --- crates/tui/src/config.rs | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 2b2f2a67..451fd967 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -1935,6 +1935,9 @@ impl Config { .with_context(|| format!("Failed to read config file: {}", path.display()))?; let parsed: ConfigFile = toml::from_str(&contents) .with_context(|| format!("Failed to parse config file: {}", path.display()))?; + if let Some(msg) = warn_on_misplaced_top_level_keys(&contents) { + tracing::warn!("{msg}"); + } apply_profile(parsed, profile)? } else { Config::default() @@ -4107,6 +4110,44 @@ fn load_single_config_file(path: &Path) -> Result { Ok(parsed.base) } +/// Build a one-line warning when top-level-only keys are nested under a section +/// CodeWhale does not define (`[general]` / `[sandbox]`). TOML silently drops +/// those keys, so e.g. `[general]\nallow_shell = true` never takes effect and +/// the shell tools (`exec_shell`, `task_shell_start`, …) are absent from the +/// catalog with no explanation. Returns `None` when nothing is misplaced. +/// +/// This is the exact confusion behind #2589: `allow_shell` and `sandbox_mode` +/// belong at the top of the file, above any `[section]` header. +fn warn_on_misplaced_top_level_keys(raw: &str) -> Option { + let doc = toml::from_str::(raw).ok()?; + // Sections CodeWhale does not recognize but users nest settings under. + const UNKNOWN_SECTIONS: &[&str] = &["general", "sandbox"]; + // Keys that are only ever read from the top level of the config. + const TOP_LEVEL_KEYS: &[&str] = &["allow_shell", "sandbox_mode", "approval_policy"]; + + let mut hits: Vec = Vec::new(); + for section in UNKNOWN_SECTIONS { + let Some(table) = doc.get(*section).and_then(toml::Value::as_table) else { + continue; + }; + for key in TOP_LEVEL_KEYS { + if table.contains_key(*key) { + hits.push(format!("`{section}.{key}`")); + } + } + } + if hits.is_empty() { + return None; + } + Some(format!( + "Ignoring {} — CodeWhale has no `[general]` or `[sandbox]` section, so these \ + keys are silently dropped. Move them to the TOP of the config file (above any \ + `[section]` header), e.g. `allow_shell = true`. Until then, shell tools stay \ + disabled. (#2589)", + hits.join(", ") + )) +} + fn apply_managed_overrides(config: &mut Config) -> Result<()> { let path = config .managed_config_path @@ -5024,6 +5065,26 @@ mod tests { ); } + #[test] + fn warns_when_allow_shell_nested_under_general_section() { + // #2589: the reporter's config nested top-level keys under sections that + // do not exist, so they were silently dropped and shell tools vanished. + let raw = "[general]\nallow_shell = true\n\n[sandbox]\nsandbox_mode = \"danger-full-access\"\n"; + let warning = + warn_on_misplaced_top_level_keys(raw).expect("misplaced keys should produce a warning"); + assert!(warning.contains("general.allow_shell")); + assert!(warning.contains("sandbox.sandbox_mode")); + assert!(warning.contains("#2589")); + + // Correctly placed top-level keys produce no warning. + let ok = "allow_shell = true\nsandbox_mode = \"danger-full-access\"\n"; + assert!(warn_on_misplaced_top_level_keys(ok).is_none()); + + // A parsed config from the correct placement actually enables shell. + let parsed: ConfigFile = toml::from_str(ok).expect("parse top-level config"); + assert!(parsed.base.allow_shell()); + } + #[test] fn update_config_defaults_to_enabled_without_uri() { let config = Config::default();