mirror of
https://github.com/openai/codex.git
synced 2026-04-28 18:32:04 +03:00
268 lines
9.3 KiB
Markdown
268 lines
9.3 KiB
Markdown
# PR #1691: Fix invisible commands while approving
|
|
|
|
- URL: https://github.com/openai/codex/pull/1691
|
|
- Author: easong-openai
|
|
- Created: 2025-07-26 19:35:50 UTC
|
|
- Updated: 2025-07-27 18:23:05 UTC
|
|
- Changes: +178/-4, Files changed: 2, Commits: 3
|
|
|
|
## Description
|
|
|
|
Fixes disappearing approvals and adds tests.
|
|
|
|
## Full Diff
|
|
|
|
```diff
|
|
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 bc647c683e..8099bf9289 100644
|
|
--- a/codex-rs/exec/src/event_processor_with_human_output.rs
|
|
+++ b/codex-rs/exec/src/event_processor_with_human_output.rs
|
|
@@ -3,10 +3,12 @@ use codex_core::config::Config;
|
|
use codex_core::protocol::AgentMessageDeltaEvent;
|
|
use codex_core::protocol::AgentMessageEvent;
|
|
use codex_core::protocol::AgentReasoningDeltaEvent;
|
|
+use codex_core::protocol::ApplyPatchApprovalRequestEvent;
|
|
use codex_core::protocol::BackgroundEventEvent;
|
|
use codex_core::protocol::ErrorEvent;
|
|
use codex_core::protocol::Event;
|
|
use codex_core::protocol::EventMsg;
|
|
+use codex_core::protocol::ExecApprovalRequestEvent;
|
|
use codex_core::protocol::ExecCommandBeginEvent;
|
|
use codex_core::protocol::ExecCommandEndEvent;
|
|
use codex_core::protocol::FileChange;
|
|
@@ -474,11 +476,45 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
|
println!("{}", line.style(self.dimmed));
|
|
}
|
|
}
|
|
- EventMsg::ExecApprovalRequest(_) => {
|
|
- // Should we exit?
|
|
+ EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
|
|
+ command,
|
|
+ cwd,
|
|
+ reason,
|
|
+ ..
|
|
+ }) => {
|
|
+ ts_println!(
|
|
+ self,
|
|
+ "{} {} in {}",
|
|
+ "approval required for".style(self.magenta),
|
|
+ escape_command(&command).style(self.bold),
|
|
+ cwd.to_string_lossy(),
|
|
+ );
|
|
+ if let Some(r) = reason {
|
|
+ ts_println!(self, "{r}");
|
|
+ }
|
|
+ return CodexStatus::InitiateShutdown;
|
|
}
|
|
- EventMsg::ApplyPatchApprovalRequest(_) => {
|
|
- // Should we exit?
|
|
+ EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {
|
|
+ changes,
|
|
+ reason,
|
|
+ ..
|
|
+ }) => {
|
|
+ ts_println!(
|
|
+ self,
|
|
+ "{}:",
|
|
+ "approval required for apply_patch".style(self.magenta),
|
|
+ );
|
|
+ for (path, change) in changes.iter() {
|
|
+ println!(
|
|
+ " {} {}",
|
|
+ format_file_change(change).style(self.cyan),
|
|
+ path.to_string_lossy(),
|
|
+ );
|
|
+ }
|
|
+ if let Some(r) = reason {
|
|
+ ts_println!(self, "{r}");
|
|
+ }
|
|
+ return CodexStatus::InitiateShutdown;
|
|
}
|
|
EventMsg::AgentReasoning(agent_reasoning_event) => {
|
|
if self.show_agent_reasoning {
|
|
@@ -538,3 +574,42 @@ fn format_file_change(change: &FileChange) -> &'static str {
|
|
} => "M",
|
|
}
|
|
}
|
|
+
|
|
+#[cfg(test)]
|
|
+mod tests {
|
|
+ use super::*;
|
|
+ use codex_core::config::Config;
|
|
+ use codex_core::config::ConfigOverrides;
|
|
+ use codex_core::config::ConfigToml;
|
|
+ use codex_core::protocol::Event;
|
|
+ use codex_core::protocol::EventMsg;
|
|
+ use codex_core::protocol::ExecApprovalRequestEvent;
|
|
+
|
|
+ fn test_config() -> Config {
|
|
+ Config::load_from_base_config_with_overrides(
|
|
+ ConfigToml::default(),
|
|
+ ConfigOverrides::default(),
|
|
+ std::env::temp_dir(),
|
|
+ )
|
|
+ .unwrap_or_else(|e| panic!("failed to load test configuration: {e}"))
|
|
+ }
|
|
+
|
|
+ #[test]
|
|
+ fn exec_approval_request_displays_command() {
|
|
+ let config = test_config();
|
|
+ let mut processor = EventProcessorWithHumanOutput::create_with_ansi(false, &config, None);
|
|
+
|
|
+ let event = Event {
|
|
+ id: "1".into(),
|
|
+ msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
|
|
+ call_id: "c1".into(),
|
|
+ command: vec!["rm".into(), "-rf".into(), "/".into()],
|
|
+ cwd: PathBuf::from("/tmp"),
|
|
+ reason: None,
|
|
+ }),
|
|
+ };
|
|
+
|
|
+ let status = processor.process_event(event);
|
|
+ assert!(matches!(status, CodexStatus::InitiateShutdown));
|
|
+ }
|
|
+}
|
|
diff --git a/codex-rs/tui/src/user_approval_widget.rs b/codex-rs/tui/src/user_approval_widget.rs
|
|
index 431f85a268..c492de19e0 100644
|
|
--- a/codex-rs/tui/src/user_approval_widget.rs
|
|
+++ b/codex-rs/tui/src/user_approval_widget.rs
|
|
@@ -368,3 +368,102 @@ impl WidgetRef for &UserApprovalWidget<'_> {
|
|
Widget::render(List::new(lines), response_chunk, buf);
|
|
}
|
|
}
|
|
+
|
|
+#[cfg(test)]
|
|
+mod tests {
|
|
+ use super::*;
|
|
+ use crate::app_event::AppEvent;
|
|
+ use crate::app_event_sender::AppEventSender;
|
|
+ use ratatui::buffer::Buffer;
|
|
+ use ratatui::layout::Rect;
|
|
+ use ratatui::widgets::WidgetRef;
|
|
+ use std::path::PathBuf;
|
|
+ use std::sync::mpsc::channel;
|
|
+
|
|
+ #[test]
|
|
+ fn exec_command_is_visible_in_small_viewport() {
|
|
+ let long_reason = "This is a very long explanatory reason that would normally occupy many lines in the confirmation prompt. \
|
|
+It should not cause the actual command or the response options to be scrolled out of the visible area.";
|
|
+
|
|
+ let (tx, _rx) = channel::<AppEvent>();
|
|
+ let app_tx = AppEventSender::new(tx);
|
|
+
|
|
+ let cwd = PathBuf::from("/home/alice/project");
|
|
+ let command = vec![
|
|
+ "bash".to_string(),
|
|
+ "-lc".to_string(),
|
|
+ "echo 123 && printf 'hello'".to_string(),
|
|
+ ];
|
|
+
|
|
+ let widget = UserApprovalWidget::new(
|
|
+ ApprovalRequest::Exec {
|
|
+ id: "test-id".to_string(),
|
|
+ command: command.clone(),
|
|
+ cwd: cwd.clone(),
|
|
+ reason: Some(long_reason.to_string()),
|
|
+ },
|
|
+ app_tx,
|
|
+ );
|
|
+
|
|
+ let area = Rect::new(0, 0, 50, 12);
|
|
+ let mut buf = Buffer::empty(area);
|
|
+ (&widget).render_ref(area, &mut buf);
|
|
+
|
|
+ let mut rendered = String::new();
|
|
+ for y in 0..area.height {
|
|
+ for x in 0..area.width {
|
|
+ let cell = &buf[(x, y)];
|
|
+ rendered.push(cell.symbol().chars().next().unwrap_or('\0'));
|
|
+ }
|
|
+ rendered.push('\n');
|
|
+ }
|
|
+
|
|
+ assert!(
|
|
+ rendered.contains("echo 123 && printf 'hello'"),
|
|
+ "rendered buffer did not contain the command.\n--- buffer ---\n{rendered}\n----------------"
|
|
+ );
|
|
+ assert!(rendered.contains("Yes (y)"));
|
|
+ }
|
|
+
|
|
+ #[test]
|
|
+ fn all_options_visible_in_reasonable_viewport() {
|
|
+ let (tx, _rx) = channel::<AppEvent>();
|
|
+ let app_tx = AppEventSender::new(tx);
|
|
+
|
|
+ let widget = UserApprovalWidget::new(
|
|
+ ApprovalRequest::Exec {
|
|
+ id: "test-id".to_string(),
|
|
+ command: vec![
|
|
+ "bash".into(),
|
|
+ "-lc".into(),
|
|
+ "echo 123 && printf 'hello'".into(),
|
|
+ ],
|
|
+ cwd: PathBuf::from("/home/alice/project"),
|
|
+ reason: Some("short reason".into()),
|
|
+ },
|
|
+ app_tx,
|
|
+ );
|
|
+
|
|
+ // Use a generous area to avoid truncation of either the prompt or the options.
|
|
+ let area = Rect::new(0, 0, 100, 30);
|
|
+ let mut buf = Buffer::empty(area);
|
|
+ (&widget).render_ref(area, &mut buf);
|
|
+
|
|
+ let mut rendered = String::new();
|
|
+ for y in 0..area.height {
|
|
+ for x in 0..area.width {
|
|
+ let cell = &buf[(x, y)];
|
|
+ rendered.push(cell.symbol().chars().next().unwrap_or('\0'));
|
|
+ }
|
|
+ rendered.push('\n');
|
|
+ }
|
|
+
|
|
+ for opt in super::SELECT_OPTIONS {
|
|
+ assert!(
|
|
+ rendered.contains(opt.label),
|
|
+ "expected option label to be visible: {}\n--- buffer ---\n{rendered}\n----------------",
|
|
+ opt.label
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+}
|
|
```
|
|
|
|
## Review Comments
|
|
|
|
### codex-rs/exec/src/event_processor_with_human_output.rs
|
|
|
|
- Created: 2025-07-27 16:37:04 UTC | Link: https://github.com/openai/codex/pull/1691#discussion_r2234053080
|
|
|
|
```diff
|
|
@@ -474,11 +476,45 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
|
println!("{}", line.style(self.dimmed));
|
|
}
|
|
}
|
|
- EventMsg::ExecApprovalRequest(_) => {
|
|
- // Should we exit?
|
|
+ EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
|
|
```
|
|
|
|
> Please change this to what it was before: the `codex exec` command is hardcoded to _never_ ask for approvals:
|
|
>
|
|
> https://github.com/openai/codex/blob/5a0079fea2d325d2638e2b1857cba0871fba6402/codex-rs/exec/src/lib.rs#L106-L108
|
|
|
|
### codex-rs/tui/src/user_approval_widget.rs
|
|
|
|
- Created: 2025-07-27 16:38:24 UTC | Link: https://github.com/openai/codex/pull/1691#discussion_r2234053504
|
|
|
|
```diff
|
|
@@ -368,3 +368,102 @@ impl WidgetRef for &UserApprovalWidget<'_> {
|
|
Widget::render(List::new(lines), response_chunk, buf);
|
|
}
|
|
}
|
|
+
|
|
+#[cfg(test)]
|
|
```
|
|
|
|
> I see tests in this PR, but no logic to change behavior to "fix invisible commands," so I'm confused. |