diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index a667cdd53d7d..c4c18d155d2c 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -217,6 +217,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { const VERSION: &str = env!("CARGO_PKG_VERSION"); eprintln!("OpenAI Codex v{VERSION}\n--------"); for (key, value) in config_summary_entries(config, session_configured_event) { + let value = sanitize_config_summary_value(&value); eprintln!("{} {}", format!("{key}:").style(self.bold), value); } eprintln!("--------"); @@ -416,6 +417,35 @@ impl EventProcessor for EventProcessorWithHumanOutput { } } +fn sanitize_config_summary_value(value: &str) -> String { + value + .chars() + .map(|ch| { + if ch.is_control() // C0/C1 controls, including newlines and escape. + || matches!( + ch, + '\u{061C}' // Arabic letter mark. + | '\u{200E}' // Left-to-right mark. + | '\u{200F}' // Right-to-left mark. + | '\u{202A}' // Left-to-right embedding. + | '\u{202B}' // Right-to-left embedding. + | '\u{202C}' // Pop directional formatting. + | '\u{202D}' // Left-to-right override. + | '\u{202E}' // Right-to-left override. + | '\u{2066}' // Left-to-right isolate. + | '\u{2067}' // Right-to-left isolate. + | '\u{2068}' // First strong isolate. + | '\u{2069}' // Pop directional isolate. + ) + { + '�' + } else { + ch + } + }) + .collect() +} + fn config_summary_entries( config: &Config, session_configured_event: &SessionConfiguredEvent, diff --git a/codex-rs/exec/src/event_processor_with_human_output_tests.rs b/codex-rs/exec/src/event_processor_with_human_output_tests.rs index 5e89d48c7f93..95df404ab36a 100644 --- a/codex-rs/exec/src/event_processor_with_human_output_tests.rs +++ b/codex-rs/exec/src/event_processor_with_human_output_tests.rs @@ -23,6 +23,7 @@ use super::EventProcessorWithHumanOutput; use super::config_summary_entries; use super::final_message_from_turn_items; use super::reasoning_text; +use super::sanitize_config_summary_value; use super::should_print_final_message_to_stdout; use super::should_print_final_message_to_tty; use crate::event_processor::EventProcessor; @@ -174,6 +175,14 @@ fn summarizes_managed_read_only_permission_profile() { ); } +#[test] +fn config_summary_values_are_single_line_terminal_text() { + assert_eq!( + sanitize_config_summary_value("repo\nmodel: other\r\x1b[2J\u{0007}\u{202E}rtl\u{2066}"), + "repo�model: other��[2J��rtl�" + ); +} + #[tokio::test] async fn config_summary_entries_include_runtime_workspace_roots() { let codex_home = tempfile::tempdir().expect("create codex home");