mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-09 01:24:49 +08:00
Lock in Linux hook stdin BrokenPipe coverage
Latest main already contains the functional BrokenPipe tolerance in plugins::hooks::CommandWithStdin::output_with_stdin, but the only coverage for the original CI failure was the higher-level plugin hook test. Add a deterministic regression that exercises the exact low-level EPIPE path by spawning a hook child that closes stdin immediately while the parent writes an oversized payload. This keeps the real root cause explicit: Linux surfaced BrokenPipe from the parent's stdin write after the hook child closed fd 0 early. Missing execute bits were not the primary bug. Constraint: Keep the change surgical on top of latest main Rejected: Re-open the production code path | latest main already contains the runtime fix Rejected: Inflate HookRunner payloads in the regression | HOOK_* env injection hit ARG_MAX before the pipe path Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep BrokenPipe coverage near CommandWithStdin so future refactors do not regress the Linux EPIPE path Tested: cargo test -p plugins hooks::tests::collects_and_runs_hooks_from_enabled_plugins -- --exact (10x) Tested: cargo test -p plugins hooks::tests::output_with_stdin_tolerates_broken_pipe_when_child_closes_stdin_early -- --exact (10x) Tested: cargo test --workspace Not-tested: GitHub Actions rerun on the PR branch
This commit is contained in:
@@ -561,4 +561,43 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_with_stdin_tolerates_broken_pipe_when_child_closes_stdin_early() {
|
||||
// given: a hook that immediately closes stdin without consuming the
|
||||
// JSON payload. Use an oversized payload so the parent keeps writing
|
||||
// long enough for Linux to surface EPIPE on the old implementation.
|
||||
let root = temp_dir("stdin-close");
|
||||
let script = root.join("close-stdin.sh");
|
||||
fs::create_dir_all(&root).expect("temp hook dir");
|
||||
fs::write(
|
||||
&script,
|
||||
"#!/bin/sh\nexec 0<&-\nprintf 'stdin closed early\\n'\nsleep 0.05\n",
|
||||
)
|
||||
.expect("write stdin-closing hook");
|
||||
make_executable(&script);
|
||||
|
||||
let mut child = super::shell_command(script.to_str().expect("utf8 path"));
|
||||
child.stdin(std::process::Stdio::piped());
|
||||
child.stdout(std::process::Stdio::piped());
|
||||
child.stderr(std::process::Stdio::piped());
|
||||
let large_input = vec![b'x'; 2 * 1024 * 1024];
|
||||
|
||||
// when
|
||||
let output = child
|
||||
.output_with_stdin(&large_input)
|
||||
.expect("broken pipe should be tolerated");
|
||||
|
||||
// then
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"child should still exit cleanly: {output:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
"stdin closed early"
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(root);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user