fix(microvm): --agents tools is a JSON array; the verifier's restriction was never applied
The 2.1.276 canary (mission 01a0b747) exited before its first API call:
Error: Invalid --agents configuration:
explorer.tools: Invalid input
verifier.tools: Invalid input
We sent `"tools": "Read, Grep, Glob, Bash"` — frontmatter syntax, where the
`--agents` JSON schema takes an array. Claude Code 2.1.243 changed invalid
agent definitions from silently ignored to a hard error, which is how the
canary caught it. The uncomfortable half of that: every CLI before 2.1.243
DROPPED the definition, so the verifier's whole guarantee — a tool allowlist
with no Edit and no Write — has plausibly never been in force on any VM run;
the lead's `Agent` calls would have fallen through to a general-purpose
subagent. The test that guarded it read the field with `as_str` and would
have kept passing on the exact string the CLI was discarding.
Both roles now send arrays; the test reads an array; a new test asserts the
shape for every role. Server-side only — no rootfs changes.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
This commit is contained in:
co-authored by
Claude Opus 5
parent
794f2124bc
commit
cbc9c2d353
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user