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
|
// 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
|
// 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.
|
// 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
|
// Foreground, against the default since 2.1.198. A background verifier
|
||||||
// lets the lead carry on and write its report before the check has
|
// lets the lead carry on and write its report before the check has
|
||||||
// finished — the finding would arrive after the conclusion. The whole
|
// 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 \
|
"description": "Reads and searches the codebase to answer a specific \
|
||||||
question. Use when finding something out would otherwise \
|
question. Use when finding something out would otherwise \
|
||||||
fill the main context with files and search output.",
|
fill the main context with files and search output.",
|
||||||
"tools": "Read, Grep, Glob",
|
"tools": ["Read", "Grep", "Glob"],
|
||||||
"prompt": format!(
|
"prompt": format!(
|
||||||
"You answer one question about this codebase by reading it. Return the \
|
"You answer one question about this codebase by reading it. Return the \
|
||||||
answer and the paths that support it — not a transcript of your \
|
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
|
/// 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.
|
/// report success, and the report is about a tree nobody reviewed.
|
||||||
#[test]
|
#[test]
|
||||||
fn the_verifier_cannot_modify_what_it_checks() {
|
fn the_verifier_cannot_modify_what_it_checks() {
|
||||||
let defs = agent_definitions();
|
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"] {
|
for forbidden in ["Edit", "Write"] {
|
||||||
assert!(
|
assert!(
|
||||||
!tools.contains(forbidden),
|
!tools.contains(forbidden),
|
||||||
|
|||||||
Reference in New Issue
Block a user