mirror of
https://github.com/openai/codex.git
synced 2026-05-23 12:34:25 +00:00
fix(tui): restore Windows VT before TUI renders (#24082)
## Why Older Git for Windows versions can leave the Windows console output mode without virtual terminal processing after Codex runs git metadata commands in a repository. When the TUI later emits ANSI control sequences for redraws, restore, or image rendering, Windows Terminal can show raw escape bytes or leave the prompt/status area corrupted. This is a targeted mitigation for the repo-conditioned Windows rendering corruption reported in #23888 and related reports #23512 and #23628. Updating Git avoids the trigger for affected users, but Codex should also reassert the terminal mode before it writes TUI control sequences. | Before | After | |---|---| | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23 21" src="https://github.com/user-attachments/assets/3218c379-5f97-4c71-ab25-805c9d20578a" /> | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23 58" src="https://github.com/user-attachments/assets/55ac72bb-37d0-400e-99bc-12dd5ea4092d" /> | ## What Changed - Re-enable Windows virtual terminal processing for stdout and stderr before TUI mode setup, restore, redraw, resume, and pet image render paths. - Treat invalid, null, or non-console handles as no-ops so redirected or non-console output is unaffected. - Keep the helper as a no-op on non-Windows platforms. ## How to Test 1. On Windows Terminal with a Git 2.28.0 for Windows install, start Codex inside a valid Git repository. 2. Start a new Codex CLI session. 3. Confirm the prompt, working indicator, and bottom status line remain readable instead of showing raw ANSI escape sequences. 4. Repeat outside a Git repository to confirm the ordinary non-repo startup path is unchanged. Targeted tests: - Not run locally; the behavior depends on Windows console mode APIs and the current worktree is on macOS.
This commit is contained in:
@@ -166,6 +166,8 @@ mod tests {
|
||||
}
|
||||
|
||||
pub fn set_modes() -> Result<()> {
|
||||
ensure_virtual_terminal_processing()?;
|
||||
|
||||
execute!(stdout(), EnableBracketedPaste)?;
|
||||
|
||||
enable_raw_mode()?;
|
||||
@@ -239,12 +241,16 @@ fn restore_common(
|
||||
raw_mode_restore: RawModeRestore,
|
||||
keyboard_restore: KeyboardRestore,
|
||||
) -> Result<()> {
|
||||
let mut first_error = ensure_virtual_terminal_processing().err();
|
||||
|
||||
match keyboard_restore {
|
||||
KeyboardRestore::PopStack => keyboard_modes::restore_keyboard_enhancement_stack(),
|
||||
KeyboardRestore::ResetAfterExit => keyboard_modes::reset_keyboard_reporting_after_exit(),
|
||||
}
|
||||
|
||||
let mut first_error = execute!(stdout(), DisableBracketedPaste).err();
|
||||
if let Err(err) = execute!(stdout(), DisableBracketedPaste) {
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
let _ = execute!(stdout(), DisableFocusChange);
|
||||
if matches!(raw_mode_restore, RawModeRestore::Disable)
|
||||
&& let Err(err) = disable_raw_mode()
|
||||
@@ -797,6 +803,8 @@ impl Tui {
|
||||
// the synchronized update, to avoid racing with the event reader.
|
||||
let mut pending_viewport_area = self.pending_viewport_area()?;
|
||||
|
||||
ensure_virtual_terminal_processing()?;
|
||||
|
||||
stdout().sync_update(|_| {
|
||||
#[cfg(unix)]
|
||||
if let Some(prepared) = prepared_resume.take() {
|
||||
@@ -854,6 +862,10 @@ impl Tui {
|
||||
&mut self,
|
||||
request: Option<crate::pets::AmbientPetDraw>,
|
||||
) -> std::result::Result<(), crate::pets::PetImageRenderError> {
|
||||
if let Err(err) = ensure_virtual_terminal_processing() {
|
||||
return Err(crate::pets::PetImageRenderError::Terminal(err));
|
||||
}
|
||||
|
||||
let terminal = &mut self.terminal;
|
||||
let state = &mut self.ambient_pet_image_state;
|
||||
stdout().sync_update(|_| {
|
||||
@@ -869,6 +881,10 @@ impl Tui {
|
||||
&mut self,
|
||||
request: Option<crate::pets::AmbientPetDraw>,
|
||||
) -> std::result::Result<(), crate::pets::PetImageRenderError> {
|
||||
if let Err(err) = ensure_virtual_terminal_processing() {
|
||||
return Err(crate::pets::PetImageRenderError::Terminal(err));
|
||||
}
|
||||
|
||||
let terminal = &mut self.terminal;
|
||||
let state = &mut self.pet_picker_preview_image_state;
|
||||
stdout().sync_update(|_| {
|
||||
@@ -887,6 +903,10 @@ impl Tui {
|
||||
pub fn clear_ambient_pet_image(
|
||||
&mut self,
|
||||
) -> std::result::Result<(), crate::pets::PetImageRenderError> {
|
||||
if let Err(err) = ensure_virtual_terminal_processing() {
|
||||
return Err(crate::pets::PetImageRenderError::Terminal(err));
|
||||
}
|
||||
|
||||
crate::pets::render_ambient_pet_image(
|
||||
self.terminal.backend_mut(),
|
||||
&mut self.ambient_pet_image_state,
|
||||
@@ -911,6 +931,8 @@ impl Tui {
|
||||
.suspend_context
|
||||
.prepare_resume_action(&mut self.terminal, &mut self.alt_saved_viewport);
|
||||
|
||||
ensure_virtual_terminal_processing()?;
|
||||
|
||||
stdout().sync_update(|_| {
|
||||
#[cfg(unix)]
|
||||
if let Some(prepared) = prepared_resume.take() {
|
||||
@@ -968,3 +990,51 @@ impl Tui {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn ensure_virtual_terminal_processing() -> Result<()> {
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
||||
use windows_sys::Win32::System::Console::ENABLE_PROCESSED_OUTPUT;
|
||||
use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
use windows_sys::Win32::System::Console::GetConsoleMode;
|
||||
use windows_sys::Win32::System::Console::GetStdHandle;
|
||||
use windows_sys::Win32::System::Console::STD_ERROR_HANDLE;
|
||||
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
|
||||
use windows_sys::Win32::System::Console::SetConsoleMode;
|
||||
|
||||
fn enable_for_handle(handle: HANDLE) -> Result<()> {
|
||||
if handle == INVALID_HANDLE_VALUE || handle == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut mode = 0;
|
||||
if unsafe { GetConsoleMode(handle, &mut mode) } == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let requested = ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
if mode & requested == requested {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if unsafe { SetConsoleMode(handle, mode | requested) } == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let stdout_handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
|
||||
enable_for_handle(stdout_handle)?;
|
||||
|
||||
let stderr_handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) };
|
||||
enable_for_handle(stderr_handle)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn ensure_virtual_terminal_processing() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user