diff --git a/crates/cm-api/src/microvm_executor.rs b/crates/cm-api/src/microvm_executor.rs index 610b9f7..ec6b2e3 100644 --- a/crates/cm-api/src/microvm_executor.rs +++ b/crates/cm-api/src/microvm_executor.rs @@ -232,7 +232,13 @@ fn agent_definitions() -> serde_json::Value { // so `acceptEdits` reaches it either way. It is also why the image is // pinned to 2.1.223: 2.1.222 fixed background subagents being able to // bypass tool restrictions, and this is the tool restriction in question. - "tools": "Read, Grep, Glob, Bash", + // A JSON ARRAY. The comma-separated string is frontmatter syntax, + // not `--agents` syntax, and every CLI before 2.1.243 dropped an + // invalid definition SILENTLY — so from the day this shipped until + // the 2.1.276 canary on 2026-09-18 refused it ("verifier.tools: + // Invalid input"), this restriction may never have been applied. + // The guarantee below is only as real as this line's shape. + "tools": ["Read", "Grep", "Glob", "Bash"], // Foreground, against the default since 2.1.198. A background verifier // lets the lead carry on and write its report before the check has // finished — the finding would arrive after the conclusion. The whole @@ -256,7 +262,7 @@ fn agent_definitions() -> serde_json::Value { "description": "Reads and searches the codebase to answer a specific \ question. Use when finding something out would otherwise \ fill the main context with files and search output.", - "tools": "Read, Grep, Glob", + "tools": ["Read", "Grep", "Glob"], "prompt": format!( "You answer one question about this codebase by reading it. Return the \ answer and the paths that support it — not a transcript of your \ @@ -1115,12 +1121,35 @@ mod tests { } } + /// The CLI's `--agents` schema takes `tools` as an array. 2.1.276 refused + /// the string form with "verifier.tools: Invalid input" and exited before + /// a single API call; every earlier CLI ignored the definition silently. + #[test] + fn every_tools_field_is_an_array_the_cli_accepts() { + for (name, d) in agent_definitions().as_object().expect("object") { + let tools = d.get("tools").unwrap_or_else(|| panic!("{name} has no tools")); + assert!(tools.is_array(), "{name}.tools must be a JSON array, got {tools}"); + assert!( + tools.as_array().unwrap().iter().all(|t| t.is_string()), + "{name}.tools entries must be strings" + ); + } + } + /// A verifier that can edit will fix what it was asked to check and then /// report success, and the report is about a tree nobody reviewed. #[test] fn the_verifier_cannot_modify_what_it_checks() { let defs = agent_definitions(); - let tools = defs["verifier"]["tools"].as_str().expect("a tools allowlist"); + // An ARRAY, as `--agents` requires. `as_str` here would have kept + // passing on the string form the CLI was silently discarding. + let tools: Vec<&str> = defs["verifier"]["tools"] + .as_array() + .expect("a tools allowlist, as a JSON array") + .iter() + .map(|t| t.as_str().expect("tool names are strings")) + .collect(); + let tools = tools.join(", "); for forbidden in ["Edit", "Write"] { assert!( !tools.contains(forbidden),