diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 2dd48bf366..024fec1fae 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -902,9 +902,11 @@ async fn submission_loop( } } - // Gather history metadata for SessionConfiguredEvent. - let (history_log_id, history_entry_count) = - crate::message_history::history_metadata(&config).await; + // Gather history metadata for SessionConfiguredEvent and await git info. + let git_info_fut = crate::git_info::collect_git_info(&sess.as_ref().unwrap().cwd); + let history_fut = crate::message_history::history_metadata(&config); + let (git_info, (history_log_id, history_entry_count)) = + tokio::join!(git_info_fut, history_fut); // ack let events = std::iter::once(Event { @@ -914,6 +916,7 @@ async fn submission_loop( model, history_log_id, history_entry_count, + git_info, }), }) .chain(mcp_connection_errors.into_iter()); diff --git a/codex-rs/core/src/git_info.rs b/codex-rs/core/src/git_info.rs index 52d029f669..506b02b76c 100644 --- a/codex-rs/core/src/git_info.rs +++ b/codex-rs/core/src/git_info.rs @@ -6,8 +6,12 @@ use tokio::process::Command; use tokio::time::Duration as TokioDuration; use tokio::time::timeout; -/// Timeout for git commands to prevent freezing on large repositories -const GIT_COMMAND_TIMEOUT: TokioDuration = TokioDuration::from_secs(5); +/// Timeout for git commands to prevent freezing on large repositories. +/// +/// Tests that wait for the initial `SessionConfigured` event use a short +/// timeout (~1s). Collecting Git info is best-effort and must not block the +/// session handshake, so we cap individual Git calls to a small value. +const GIT_COMMAND_TIMEOUT: TokioDuration = TokioDuration::from_millis(400); #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GitInfo { diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index 4972f10d98..5db3cd7c48 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -19,6 +19,7 @@ use uuid::Uuid; use crate::config_types::ReasoningEffort as ReasoningEffortConfig; use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; +use crate::git_info::GitInfo; use crate::message_history::HistoryEntry; use crate::model_provider_info::ModelProviderInfo; use crate::parse_command::ParsedCommand; @@ -695,6 +696,10 @@ pub struct SessionConfiguredEvent { /// Current number of entries in the history log. pub history_entry_count: usize, + + /// Optional Git metadata for the configured cwd. + #[serde(skip_serializing_if = "Option::is_none")] + pub git_info: Option, } /// User's decision in response to an ExecApprovalRequest. @@ -757,6 +762,7 @@ mod tests { model: "codex-mini-latest".to_string(), history_log_id: 0, history_entry_count: 0, + git_info: None, }), }; let serialized = serde_json::to_string(&event).unwrap(); 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 1d35dcb73f..aca6a75592 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -494,6 +494,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { model, history_log_id: _, history_entry_count: _, + git_info: _, } = session_configured_event; ts_println!( diff --git a/codex-rs/mcp-server/src/mcp_protocol.rs b/codex-rs/mcp-server/src/mcp_protocol.rs index 0528e18a39..426352e647 100644 --- a/codex-rs/mcp-server/src/mcp_protocol.rs +++ b/codex-rs/mcp-server/src/mcp_protocol.rs @@ -908,6 +908,7 @@ mod tests { model: "codex-mini-latest".into(), history_log_id: 42, history_entry_count: 3, + git_info: None, }), }; diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index e7b0b9b63c..9528e35fbf 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -244,6 +244,7 @@ mod tests { model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, + git_info: None, }), }; @@ -284,6 +285,7 @@ mod tests { model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, + git_info: None, }; let event = Event { id: "1".to_string(), diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index b49e59972c..97a1dd865a 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -233,6 +233,7 @@ impl HistoryCell { session_id: _, history_log_id: _, history_entry_count: _, + git_info, } = event; if is_first_event { let cwd_str = match relativize_to_home(&config.cwd) { @@ -241,6 +242,18 @@ impl HistoryCell { None => config.cwd.display().to_string(), }; + // Use async-collected Git info from the event if available. + let branch_suffix = git_info + .and_then(|g| g.branch) + .map(|b| format!(" ({b})")) + .unwrap_or_default(); + + let path_and_branch = if branch_suffix.is_empty() { + format!(" {cwd_str}") + } else { + format!(" {cwd_str}{branch_suffix}") + }; + let lines: Vec> = vec![ Line::from(vec![ Span::raw(">_ ").dim(), @@ -248,7 +261,7 @@ impl HistoryCell { "You are using OpenAI Codex in", Style::default().add_modifier(Modifier::BOLD), ), - Span::raw(format!(" {cwd_str}")).dim(), + Span::raw(path_and_branch).dim(), ]), Line::from("".dim()), Line::from(" To get started, describe a task or try one of these commands:".dim()), @@ -883,6 +896,8 @@ impl HistoryCell { } } +// + impl WidgetRef for &HistoryCell { fn render_ref(&self, area: Rect, buf: &mut Buffer) { Paragraph::new(Text::from(self.plain_lines()))