fix(tools): cut RemoteTrigger responses on a char boundary (#3369)

* fix(tools): cut RemoteTrigger responses on a char boundary

A response over 8192 bytes with a multi-byte character across byte 8192 panicked on the slice.

* test(tools): add multibyte char boundary truncation test for RemoteTrigger

---------

Co-authored-by: clawdbot <clawdbot@ultraworkers.dev>
This commit is contained in:
Iainandclawdbot authored and GitHub committed 2026-11-02 00:03:39 +00:00
1 parent f4fff38de1
commit a289024b35
1 file changed
+49 -1
+49 -1
View File
@@ -2096,9 +2096,13 @@ fn run_remote_trigger(input: RemoteTriggerInput) -> Result<String, String> {
let status = response.status().as_u16();
let body = response.text().unwrap_or_default();
let truncated_body = if body.len() > 8192 {
let mut end = 8192;
while !body.is_char_boundary(end) {
end -= 1;
}
format!(
"{}\n\n[response truncated — {} bytes total]",
&body[..8192],
&body[..end],
body.len()
)
} else {
@@ -11437,4 +11441,48 @@ printf 'pwsh:%s' "$1"
.collect::<Vec<_>>();
assert_eq!(answered, vec!["toolu_multi_read", "toolu_multi_grep"]);
}
#[test]
fn remote_trigger_truncates_multibyte_char_at_boundary() {
// This test verifies that run_remote_trigger safely truncates a body
// when a multibyte UTF-8 character spans the 8192-byte boundary.
// Without the char_boundary check, &body[..8192] would panic.
let server = TestServer::spawn(Arc::new(|_request| {
// Create a body that's >8192 bytes with a 4-byte character (🦀) at byte 8192.
// Strategy: fill with ASCII to reach byte 8189, then place the 4-byte crab emoji
// so that byte 8192 falls in the middle of it.
let mut body = String::new();
// 8189 ASCII 'a' characters = 8189 bytes
for _ in 0..8189 {
body.push('a');
}
// Crab emoji (🦀) is 4 bytes in UTF-8: F0 9F A6 80
// Placing it now puts byte 8192 inside it (byte 8189+3=8192 is the 4th byte of the emoji)
body.push_str("🦀");
// Add more content to make it clear we truncated
body.push_str("extra_content_after_boundary");
HttpResponse::text(200, "OK", &body)
}));
let input = json!({
"url": format!("http://{}/test", server.addr()),
"method": "GET"
});
// This should not panic on the current head (with char_boundary check).
// On main (with &body[..8192]), this would panic because byte 8192
// is in the middle of the 4-byte crab emoji.
let result = execute_tool("RemoteTrigger", &input).expect("tool should succeed");
let output: serde_json::Value =
serde_json::from_str(&result).expect("result should be valid JSON");
// Verify the response was truncated
let body = output["body"].as_str().expect("body should be string");
assert!(body.contains("[response truncated"));
// The body should not contain the full extra_content_after_boundary
assert!(!body.contains("extra_content_after_boundary"));
// The truncation should have left us with valid UTF-8 (no partial emoji)
let _ = body.to_string(); // This validates it's valid Rust string (which is valid UTF-8)
}
}