feat(tools): wire config.trusted_roots into WorkerCreate tool

Previously WorkerCreate passed trusted_roots directly to spawn_worker
with no config-level default. Any batch script omitting the field
stalled all workers at TrustRequired with no recovery path.

Now run_worker_create loads RuntimeConfig from the worker CWD before
spawning and merges config.trusted_roots() with per-call overrides.
Per-call overrides still take effect; config provides the default.

Add test: worker_create_merges_config_trusted_roots_without_per_call_override
- writes .claw/settings.json with trustedRoots=[<os-temp-dir>] in a temp worktree
- calls WorkerCreate with no trusted_roots field
- asserts trust_auto_resolve=true (config roots matched the CWD)

81 tool tests passing, 0 failing.
This commit is contained in:
YeonGyu-Kim
2026-04-08 03:08:13 +09:00
parent bcdc52d72c
commit 6ddfa78b7c

View File

@@ -1427,9 +1427,20 @@ fn run_task_output(input: TaskIdInput) -> Result<String, String> {
#[allow(clippy::needless_pass_by_value)]
fn run_worker_create(input: WorkerCreateInput) -> Result<String, String> {
// Merge config-level trusted_roots with per-call overrides.
// Config provides the default allowlist; per-call roots add on top.
let config_roots: Vec<String> = ConfigLoader::default_for(&input.cwd)
.load()
.ok()
.map(|c| c.trusted_roots().to_vec())
.unwrap_or_default();
let merged_roots: Vec<String> = config_roots
.into_iter()
.chain(input.trusted_roots.iter().cloned())
.collect();
let worker = global_worker_registry().create(
&input.cwd,
&input.trusted_roots,
&merged_roots,
input.auto_recover_prompt_misdelivery,
);
to_pretty_json(worker)
@@ -5506,6 +5517,43 @@ mod tests {
assert_eq!(accepted_output["prompt_in_flight"], true);
}
#[test]
fn worker_create_merges_config_trusted_roots_without_per_call_override() {
use std::fs;
// Write a .claw/settings.json in a temp dir with trustedRoots
let worktree = temp_path("config-trust-worktree");
let claw_dir = worktree.join(".claw");
fs::create_dir_all(&claw_dir).expect("create .claw dir");
// Use the actual OS temp dir so the worktree path matches the allowlist
let tmp_root = std::env::temp_dir().to_str().expect("utf-8").to_string();
let settings = format!("{{\"trustedRoots\": [\"{tmp_root}\"]}}");
fs::write(
claw_dir.join("settings.json"),
settings,
)
.expect("write settings");
// WorkerCreate with no per-call trusted_roots — config should supply them
let cwd = worktree.to_str().expect("valid utf-8").to_string();
let created = execute_tool(
"WorkerCreate",
&json!({
"cwd": cwd
// trusted_roots intentionally omitted
}),
)
.expect("WorkerCreate should succeed");
let output: serde_json::Value = serde_json::from_str(&created).expect("json");
// worktree is under /tmp, so config roots auto-resolve trust
assert_eq!(
output["trust_auto_resolve"], true,
"config-level trustedRoots should auto-resolve trust without per-call override"
);
fs::remove_dir_all(&worktree).ok();
}
#[test]
fn worker_tools_detect_misdelivery_and_arm_prompt_replay() {
let created = execute_tool(