diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index d74ec9fc..d11dc6ea 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -40,6 +40,10 @@ const EXIT_CODE_SIGNAL_BASE: i32 = 128; // conventional shell: 128 + signal const READ_CHUNK_SIZE: usize = 8192; // bytes per read const AGGREGATE_BUFFER_INITIAL_CAPACITY: usize = 8 * 1024; // 8 KiB +/// Limit the number of ExecCommandOutputDelta events emitted per exec call. +/// Aggregation still collects full output; only the live event stream is capped. +pub(crate) const MAX_EXEC_OUTPUT_DELTAS_PER_CALL: usize = 10_000; + #[derive(Debug, Clone)] pub struct ExecParams { pub command: Vec, @@ -344,6 +348,7 @@ async fn read_capped( ) -> io::Result>> { let mut buf = Vec::with_capacity(AGGREGATE_BUFFER_INITIAL_CAPACITY); let mut tmp = [0u8; READ_CHUNK_SIZE]; + let mut emitted_deltas: usize = 0; // No caps: append all bytes @@ -353,7 +358,9 @@ async fn read_capped( break; } - if let Some(stream) = &stream { + if let Some(stream) = &stream + && emitted_deltas < MAX_EXEC_OUTPUT_DELTAS_PER_CALL + { let chunk = tmp[..n].to_vec(); let msg = EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent { call_id: stream.call_id.clone(), @@ -370,6 +377,7 @@ async fn read_capped( }; #[allow(clippy::let_unit_value)] let _ = stream.tx_event.send(event).await; + emitted_deltas += 1; } if let Some(tx) = &aggregate_tx {