diff --git a/codex-rs/core/src/unified_exec/session.rs b/codex-rs/core/src/unified_exec/session.rs index 02465538ec..9d2198c574 100644 --- a/codex-rs/core/src/unified_exec/session.rs +++ b/codex-rs/core/src/unified_exec/session.rs @@ -31,8 +31,25 @@ pub(crate) struct OutputBufferState { impl OutputBufferState { pub(super) fn push_chunk(&mut self, chunk: Vec) { - self.total_bytes = self.total_bytes.saturating_add(chunk.len()); - self.chunks.push_back(chunk); + if chunk.is_empty() { + return; + } + + // On Windows (especially with ConPTY) output can arrive in many tiny chunks. + // Coalesce them to avoid pathological behavior in drain/collect paths. + const MAX_COALESCED_CHUNK_BYTES: usize = 8_192; + + let mut chunk = chunk; + let chunk_len = chunk.len(); + self.total_bytes = self.total_bytes.saturating_add(chunk_len); + + if let Some(tail) = self.chunks.back_mut() + && tail.len().saturating_add(chunk_len) <= MAX_COALESCED_CHUNK_BYTES + { + tail.append(&mut chunk); + } else { + self.chunks.push_back(chunk); + } let mut excess = self .total_bytes diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 9365834500..26da3048af 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -2427,14 +2427,15 @@ async fn windows_unified_exec_write_stdin_strips_escapes() -> Result<()> { } = builder.build(&server).await?; let open_call_id = "windows-uexec-open-stdin"; + let script = "$esc=[char]27; while ($true) { $line=[Console]::In.ReadLine(); if ($null -eq $line) { break }; Write-Output ($esc + '[31mUEXEC-WINDOWS-STDIN' + $esc + '[0m') }"; let open_args = json!({ - "cmd": "powershell -NoLogo -NoProfile", + "cmd": script, "yield_time_ms": 2_000, }); let stdin_call_id = "windows-uexec-stdin-escapes"; let stdin_args = json!({ - "chars": "Write-Output \"UEXEC-WINDOWS-STDIN\"\n", + "chars": "trigger\r\n", "session_id": 1000, "yield_time_ms": 5_000, });