fix(developer): byte-bound the shell truncation preview (#10992)

This commit is contained in:
filip
2026-08-05 19:11:19 -07:00
committed by GitHub
parent 86eec2a8ed
commit 3b3719de4e
@@ -158,6 +158,7 @@ fn unix_shell() -> String {
const OUTPUT_LIMIT_LINES: usize = 2000;
pub const OUTPUT_LIMIT_BYTES: usize = 50_000;
const OUTPUT_PREVIEW_LINES: usize = 50;
const OUTPUT_PREVIEW_BYTES: usize = 10_000;
const OUTPUT_SLOTS: usize = 8;
@@ -890,7 +891,7 @@ fn truncate_output(
let output_path = save_full_output(full_output, label, output_dir)?;
let preview_start = total_lines.saturating_sub(OUTPUT_PREVIEW_LINES);
let preview = lines[preview_start..].join("\n");
let preview = truncate_preview_bytes(lines[preview_start..].join("\n"));
let reason = if exceeded_lines {
format!("Output exceeded {OUTPUT_LIMIT_LINES} line limit ({total_lines} lines total).")
@@ -910,6 +911,21 @@ fn truncate_output(
})
}
/// Keep the preview's tail within OUTPUT_PREVIEW_BYTES so lines without
/// newlines (progress bars using `\r`, minified or base64 content) cannot
/// smuggle an unbounded preview past the line-based truncation.
#[allow(clippy::string_slice)] // The start index is snapped to a char boundary.
fn truncate_preview_bytes(preview: String) -> String {
if preview.len() <= OUTPUT_PREVIEW_BYTES {
return preview;
}
let mut start = preview.len() - OUTPUT_PREVIEW_BYTES;
while !preview.is_char_boundary(start) {
start += 1;
}
preview[start..].to_string()
}
fn save_full_output(
output: &str,
label: &str,
@@ -1200,6 +1216,30 @@ mod tests {
assert!(notice.contains("Full output saved to"));
}
#[test]
fn render_output_preview_is_byte_bounded_for_giant_lines() {
let dir = tempfile::tempdir().unwrap();
let input = "x".repeat(200_000);
let result = render_output(&input, "test_giant_line", dir.path()).unwrap();
assert!(result.text.len() <= OUTPUT_PREVIEW_BYTES);
let info = result
.truncation
.as_ref()
.expect("expected truncation info");
assert!(info.reason.contains("byte limit"));
assert_eq!(std::fs::read_to_string(&info.path).unwrap().len(), 200_000);
}
#[test]
fn truncate_preview_bytes_respects_char_boundaries() {
let preview = "é".repeat(OUTPUT_PREVIEW_BYTES);
let truncated = truncate_preview_bytes(preview);
assert!(truncated.len() <= OUTPUT_PREVIEW_BYTES);
assert!(truncated.chars().all(|c| c == 'é'));
}
#[test]
fn save_full_output_reuses_same_path() {
let dir = tempfile::tempdir().unwrap();