Skip to content

Commit c08f060

Browse files
committed
test(tools): end-to-end stall-detect and recovery loop coverage
Proves the clawhip restart/recover flow that gaebal-gajae flagged: 1. stall_detect_and_resolve_trust_end_to_end - Worker created without trusted_roots -> trust_auto_resolve=false - WorkerObserve with trust-prompt text -> status=trust_required, gate cleared=false - WorkerResolveTrust -> status=spawning, trust_gate_cleared=true - WorkerObserve with ready text -> status=ready_for_prompt Full resolve path verified end-to-end. 2. stall_detect_and_restart_recovery_end_to_end - Worker stalls at trust_required - WorkerRestart resets to spawning, trust_gate_cleared=false Documents the restart-then-re-acquire-trust flow. Note: seconds_since_update is in .claw/worker-state.json (state file), not in the Worker tool output struct. Staleness detection via state file is covered by emit_state_file_writes_worker_status_on_transition in worker_boot.rs tests. 87 tool tests passing, 0 failing.
1 parent cae1141 commit c08f060

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

rust/crates/tools/src/lib.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5618,6 +5618,101 @@ mod tests {
56185618
);
56195619
}
56205620

5621+
#[test]
5622+
fn stall_detect_and_resolve_trust_end_to_end() {
5623+
// 1. Create worker WITHOUT trusted_roots so trust won't auto-resolve
5624+
let created = execute_tool(
5625+
"WorkerCreate",
5626+
&json!({"cwd": "/no/trusted/root/here"}),
5627+
)
5628+
.expect("WorkerCreate should succeed");
5629+
let created_output: serde_json::Value = serde_json::from_str(&created).expect("json");
5630+
let worker_id = created_output["worker_id"].as_str().expect("worker_id").to_string();
5631+
assert_eq!(created_output["trust_auto_resolve"], false);
5632+
5633+
// 2. Observe trust prompt screen text -> worker stalls at trust_required
5634+
let stalled = execute_tool(
5635+
"WorkerObserve",
5636+
&json!({
5637+
"worker_id": worker_id,
5638+
"screen_text": "Do you trust the files in this folder?\n[Allow] [Deny]"
5639+
}),
5640+
)
5641+
.expect("WorkerObserve should succeed");
5642+
let stalled_output: serde_json::Value = serde_json::from_str(&stalled).expect("json");
5643+
assert_eq!(
5644+
stalled_output["status"], "trust_required",
5645+
"worker should stall at trust_required when trust prompt seen without allowlist"
5646+
);
5647+
assert_eq!(stalled_output["trust_gate_cleared"], false);
5648+
// 3. Clawhip calls WorkerResolveTrust to unblock
5649+
let resolved = execute_tool(
5650+
"WorkerResolveTrust",
5651+
&json!({"worker_id": worker_id}),
5652+
)
5653+
.expect("WorkerResolveTrust should succeed");
5654+
let resolved_output: serde_json::Value = serde_json::from_str(&resolved).expect("json");
5655+
assert_eq!(
5656+
resolved_output["status"], "spawning",
5657+
"worker should return to spawning after trust resolved"
5658+
);
5659+
assert_eq!(resolved_output["trust_gate_cleared"], true);
5660+
5661+
// 4. Ready screen text now advances worker normally
5662+
let ready = execute_tool(
5663+
"WorkerObserve",
5664+
&json!({
5665+
"worker_id": worker_id,
5666+
"screen_text": "Ready for input\n>"
5667+
}),
5668+
)
5669+
.expect("WorkerObserve should succeed after trust resolved");
5670+
let ready_output: serde_json::Value = serde_json::from_str(&ready).expect("json");
5671+
assert_eq!(
5672+
ready_output["status"], "ready_for_prompt",
5673+
"worker should reach ready_for_prompt after trust resolved and ready screen seen"
5674+
);
5675+
}
5676+
5677+
#[test]
5678+
fn stall_detect_and_restart_recovery_end_to_end() {
5679+
// Worker stalls at trust_required, clawhip restarts instead of resolving
5680+
let created = execute_tool(
5681+
"WorkerCreate",
5682+
&json!({"cwd": "/no/trusted/root/restart-test"}),
5683+
)
5684+
.expect("WorkerCreate should succeed");
5685+
let created_output: serde_json::Value = serde_json::from_str(&created).expect("json");
5686+
let worker_id = created_output["worker_id"].as_str().expect("worker_id").to_string();
5687+
5688+
// Force trust_required
5689+
let stalled = execute_tool(
5690+
"WorkerObserve",
5691+
&json!({
5692+
"worker_id": worker_id,
5693+
"screen_text": "trust this folder? [Yes] [No]"
5694+
}),
5695+
)
5696+
.expect("WorkerObserve should succeed");
5697+
let stalled_output: serde_json::Value = serde_json::from_str(&stalled).expect("json");
5698+
assert_eq!(stalled_output["status"], "trust_required");
5699+
5700+
// WorkerRestart resets the worker
5701+
let restarted = execute_tool(
5702+
"WorkerRestart",
5703+
&json!({"worker_id": worker_id}),
5704+
)
5705+
.expect("WorkerRestart should succeed");
5706+
let restarted_output: serde_json::Value = serde_json::from_str(&restarted).expect("json");
5707+
assert_eq!(
5708+
restarted_output["status"], "spawning",
5709+
"restarted worker should be back at spawning"
5710+
);
5711+
assert_eq!(restarted_output["trust_gate_cleared"], false,
5712+
"restart clears trust — next observe loop must re-acquire trust"
5713+
);
5714+
}
5715+
56215716
#[test]
56225717
fn worker_terminate_on_unknown_id_returns_error() {
56235718
let result = execute_tool(

0 commit comments

Comments
 (0)