#1005: vectorcraft-cli run ends quietly when its reader closes early

`run` reported "Broken pipe" (on Windows "The pipe is being closed") and exited 1 when the
reader of its stdout went away, unlike `commands` and the other subcommands, which end quietly.

Its step printer now treats a closed pipe as the end of output, not an error: the remaining
steps still run (an export is still written), unprinted, and the run exits 0. Other write
errors and failing steps still fail.

The test runs document.json, a rectangle and an SVG export with stdout closed: status 0, empty
stderr, the SVG written; `run --cmd not-a-command` still fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	ROADMAP.md
This commit is contained in:
bflatastic committed 2026-10-11 03:51:23 +07:00
1 parent 148ef1870a
commit 33b6a6323d
3 files changed
+34 -2

No files matched your search

+15 -1
View File
@@ -292,7 +292,21 @@ fn run(args: &[String]) -> Result<(), String> {
let mut h = Headless::new();
let mut out = std::io::stdout().lock();
let mut emit = |v: Value| writeln!(out, "{v}").map_err(|e| e.to_string());
// A reader that stopped early (a closed pipe) isn't an error, and the batch goes on: its later
// steps (an export…) still run, unprinted.
let mut closed = false;
let mut emit = |v: Value| {
if closed {
return Ok(());
}
match writeln!(out, "{v}") {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
closed = true;
Ok(())
}
r => r.map_err(|e| e.to_string()),
}
};
if let Some(path) = &input {
let r = h.call("app.open", json!({"path": path})).map_err(|e| format!("open {path}: {e}"))?;
emit(json!({"step": "open", "path": path, "result": r}))?;
+18
View File
@@ -297,3 +297,21 @@ fn closed_stdout_ends_quietly() {
let err = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success() && !err.contains("panicked"), "{:?}: {err}", out.status);
}
/// #1005: `run` whose reader has gone (`vectorcraft-cli run … | true`) ends quietly with status 0,
/// and its later steps still run; a failing step still fails.
#[test]
fn run_with_a_closed_stdout_ends_quietly_and_finishes_the_batch() {
let svg = tmp("closed-run.svg");
let _ = std::fs::remove_file(&svg);
let rect = r#"{"x":1,"y":1,"width":5,"height":5}"#;
let args = ["run", "--cmd", "document.json", "--cmd", "shape.rectangle", "--params", rect, "--export", svg.to_str().unwrap()];
let mut child = Command::new(BIN).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
drop(child.stdout.take());
let out = child.wait_with_output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success() && err.is_empty(), "{:?}: {err}", out.status);
assert!(svg.is_file(), "the export after the closed reader still ran");
let out = Command::new(BIN).args(["run", "--cmd", "not-a-command"]).stdout(Stdio::null()).output().unwrap();
assert!(!out.status.success());
}