ask: the binary reads --system-file through the capped reader

main.rs still called read_to_string, leaving read_system_file dead. Binary-level
tests cover a FIFO, /dev/zero, 64KiB+1 and exactly 64KiB.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-11 08:14:44 +00:00
commit 2a65c4e7a2
2 changed files with 64 additions and 3 deletions

View file

@ -102,9 +102,7 @@ fn run_ask(args: &[&str]) -> Result<(), Failure> {
flags.single.get("--system-file"),
) {
(Some(text), None) => text.clone(),
(None, Some(path)) => {
std::fs::read_to_string(path).map_err(|_| Failure::Malformed("--system-file"))?
}
(None, Some(path)) => ask::read_system_file(std::path::Path::new(path))?,
_ => return Err(Failure::Missing("--system or --system-file (exactly one)")),
};
let effort = match flags.single.get("--effort").map(String::as_str) {

View file

@ -704,3 +704,66 @@ fn the_binary_fails_loudly_with_one_stderr_line_and_empty_stdout() {
}
assert!(rig.seen().is_empty());
}
/// Runs the binary with `--system-file` instead of `--system`, killing it if it
/// has not exited within five seconds (a hang is a failure, not a stuck suite).
fn run_with_system_file(rig: &Rig, file: &std::path::Path) -> std::process::Output {
use std::io::Write;
let mut child = Command::new(env!("CARGO_BIN_EXE_claude-connector"))
.args(["ask", "--endpoint", &rig.endpoint.to_string()])
.args(["--caller-id", CALLER, "--model", MODEL])
.args(["--max-output-tokens", "1024"])
.args(["--connection-key-file", rig.key_file.to_str().unwrap()])
.args(["--system-file", file.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let _ = child.stdin.take().unwrap().write_all(b"question");
let started = std::time::Instant::now();
while child.try_wait().unwrap().is_none() {
if started.elapsed() > Duration::from_secs(5) {
child.kill().unwrap();
let _ = child.wait();
panic!("the binary hung on its --system-file");
}
thread::sleep(Duration::from_millis(20));
}
child.wait_with_output().unwrap()
}
#[test]
fn the_binary_bounds_and_guards_its_system_file() {
use claude_connector::ask::MAX_SYSTEM_BYTES;
let rig = rig(Script::Text("accepted"));
let fifo = rig.dir.join("system.fifo");
let path = std::ffi::CString::new(fifo.to_str().unwrap()).unwrap();
assert_eq!(unsafe { libc::mkfifo(path.as_ptr(), 0o600) }, 0);
let out = run_with_system_file(&rig, &fifo);
assert!(!out.status.success());
assert!(out.stdout.is_empty());
let zero = run_with_system_file(&rig, std::path::Path::new("/dev/zero"));
assert!(!zero.status.success());
assert!(zero.stdout.is_empty());
let file = rig.dir.join("system.txt");
std::fs::write(&file, vec![b's'; MAX_SYSTEM_BYTES + 1]).unwrap();
let over = run_with_system_file(&rig, &file);
assert!(!over.status.success());
assert!(over.stdout.is_empty());
assert!(String::from_utf8_lossy(&over.stderr).contains("larger than"));
assert!(rig.seen().is_empty());
std::fs::write(&file, vec![b's'; MAX_SYSTEM_BYTES]).unwrap();
let exact = run_with_system_file(&rig, &file);
assert!(
exact.status.success(),
"{}",
String::from_utf8_lossy(&exact.stderr)
);
assert_eq!(String::from_utf8(exact.stdout).unwrap(), "accepted");
assert_eq!(rig.seen()[0].instructions.len(), MAX_SYSTEM_BYTES);
}