Summary
read_limited() / the memory-management view use String::truncate(max_bytes), which panics when max_bytes does not fall on a UTF-8 char boundary. The project-memory files are markdown that routinely contains CJK (the repo itself stores zh-CN project memory), so an oversized memory file with a multi-byte char straddling the cap crashes the request.
Details
crates/harness-server/src/project_memory.rs:499-505:
fn read_limited(path: &Path, max_bytes: usize) -> std::io::Result<String> {
let mut s = std::fs::read_to_string(path)?;
if s.len() > max_bytes {
s.truncate(max_bytes); // panics if max_bytes is not a char boundary
...
- Same pattern in the management view at
project_memory.rs:158-161 (truncated.truncate(MAX_MANAGED_FILE_BYTES)).
read_limited is the hot path: it's called by load_project_memory_prompt (project_memory.rs:93-96) with config.max_bytes (index) and MAX_PROMPT_FILE_BYTES = 12_000 (kanban/calendar). load_project_memory_prompt runs via the project binder on every project-bound chat turn, so a single oversized CJK-heavy memory file takes down the turn; the line-158 variant takes down the memory-management REST view.
String::truncate documents: "Panics if new_len does not lie on a char boundary."
Impact
A reproducible panic on a normal data condition (a memory file just over the cap), reachable on the main chat path. Severity: high.
Suggested fix
Truncate to the largest char boundary <= max_bytes (e.g. str::floor_char_boundary, or walk back from max_bytes while !s.is_char_boundary(i)), then push the warning suffix.
Summary
read_limited()/ the memory-management view useString::truncate(max_bytes), which panics whenmax_bytesdoes not fall on a UTF-8 char boundary. The project-memory files are markdown that routinely contains CJK (the repo itself stores zh-CN project memory), so an oversized memory file with a multi-byte char straddling the cap crashes the request.Details
crates/harness-server/src/project_memory.rs:499-505:project_memory.rs:158-161(truncated.truncate(MAX_MANAGED_FILE_BYTES)).read_limitedis the hot path: it's called byload_project_memory_prompt(project_memory.rs:93-96) withconfig.max_bytes(index) andMAX_PROMPT_FILE_BYTES = 12_000(kanban/calendar).load_project_memory_promptruns via the project binder on every project-bound chat turn, so a single oversized CJK-heavy memory file takes down the turn; the line-158 variant takes down the memory-management REST view.String::truncatedocuments: "Panics if new_len does not lie on a char boundary."Impact
A reproducible panic on a normal data condition (a memory file just over the cap), reachable on the main chat path. Severity: high.
Suggested fix
Truncate to the largest char boundary
<= max_bytes(e.g.str::floor_char_boundary, or walk back frommax_byteswhile!s.is_char_boundary(i)), then push the warning suffix.