From 80a33f0e86271a2f1f363374eccabe11388cf61f Mon Sep 17 00:00:00 2001 From: Anubhav Rai Date: Wed, 28 Oct 2026 00:03:47 +0530 Subject: [PATCH] automation: an array where the output goes is no output either (#2479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `file.createProxy` and `renderQueue.add` read a single string (`str_p`), so `{path: ["…/a.mov"]}` leaves them on their default while the confinement saw a path it could check and let the queue go ahead: on an unsaved project that was `~/Movies/A_proxy.mov` again. Only a plain string now counts as an output the agent named. Found by CodeRabbit on the pull request, which reported it fixed by 9cc23209; it was not, and the case is in the test now. Co-Authored-By: Claude Opus 5 --- crates/automation/src/confine.rs | 7 ++++--- crates/automation/src/tests.rs | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/automation/src/confine.rs b/crates/automation/src/confine.rs index d9807ca40..ce6717f30 100644 --- a/crates/automation/src/confine.rs +++ b/crates/automation/src/confine.rs @@ -169,8 +169,9 @@ impl Confinement { (None, None) => (&[][..], Access::Read), }; let mut given = false; - // Whether one of them is a file and not a Render Queue template, whose own output is - // relative and resolved like one the agent never gave. + // Whether one of them is a file: not a Render Queue template, whose own output is relative + // and resolved like one the agent never gave, and not an array, which the commands that + // take a single output read as nothing at all. let mut gave_a_file = false; for key in named { let Some(v) = params.get(*key).filter(|v| !v.is_null()) else { continue }; @@ -184,7 +185,7 @@ impl Confinement { match p.as_str() { Some(s) if id == "render.preRender" && !looks_like_path(s) => {} Some(s) => { - gave_a_file = true; + gave_a_file |= v.is_string(); self.check(s, access)?; } None => return Err(Error::BadArgs(format!("`{id}`: `{key}` must be a path"))), diff --git a/crates/automation/src/tests.rs b/crates/automation/src/tests.rs index 3c5620fbf..61a726d6b 100644 --- a/crates/automation/src/tests.rs +++ b/crates/automation/src/tests.rs @@ -1402,6 +1402,10 @@ fn automation_roots_refuse_a_queued_output_from_an_unsaved_project() { let msg = c[0]["text"].as_str().unwrap_or_default(); assert!(msg.contains("the project has no file yet"), "`{command}`: {msg}"); } + // An array where a path goes: the engine reads a scalar, so this is no output either. + let (c, err) = call(&mut s, "execute_command", json!({"command": "file.createProxy", "params": {"comp": "A", "path": [p("arr.mov")]}})); + assert!(err, "an array where the output goes should be refused: {c:?}"); + assert!(c[0]["text"].as_str().unwrap_or_default().contains("the project has no file yet"), "{c:?}"); // Naming an Output Module template instead of a file is the same: its own output is relative. let (c, err) = call(&mut s, "execute_command", json!({"command": "render.preRender", "params": {"comp": "A", "output": "Lossless"}})); assert!(err, "a template output should be refused too: {c:?}");