fix: server-side 20s blocking in get_test_result to prevent agent poll spam

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dave
2026-04-11 22:29:38 +00:00
parent 028bff5ef1
commit fc89be2f55
+43 -3
View File
@@ -471,11 +471,15 @@ pub(super) async fn tool_run_tests(args: &Value, ctx: &AppContext) -> Result<Str
.map_err(|e| format!("Serialization error: {e}")) .map_err(|e| format!("Serialization error: {e}"))
} }
/// How long `get_test_result` blocks server-side before returning "running".
/// This prevents agents from burning turns polling every 2 seconds.
const TEST_POLL_BLOCK_SECS: u64 = 20;
/// Check on a running test job and return results if complete. /// Check on a running test job and return results if complete.
/// ///
/// Returns `{"status": "running", "elapsed_secs": N}` if still in progress, /// Blocks for up to 15 seconds, checking every second. Returns immediately
/// or the full test result if finished. If no job exists for the worktree, /// when the test finishes, or after 15s with `{"status": "running"}`.
/// returns an error. /// This server-side blocking prevents agents from wasting turns polling.
pub(super) async fn tool_get_test_result( pub(super) async fn tool_get_test_result(
args: &Value, args: &Value,
ctx: &AppContext, ctx: &AppContext,
@@ -489,6 +493,42 @@ pub(super) async fn tool_get_test_result(
.map_err(|e| format!("Cannot canonicalize project root: {e}"))?, .map_err(|e| format!("Cannot canonicalize project root: {e}"))?,
}; };
// Block for up to TEST_POLL_BLOCK_SECS, checking once per second.
let test_jobs = ctx.test_jobs.clone();
let wd = working_dir.clone();
for _ in 0..TEST_POLL_BLOCK_SECS {
{
let mut jobs = test_jobs.lock().map_err(|e| e.to_string())?;
if let Some(job) = jobs.get_mut(&wd) {
if let Some(child) = job.child.as_mut() {
match child.try_wait() {
Ok(Some(status)) => {
let result = collect_child_result(child, status);
job.child = None;
job.result = Some(result.clone());
jobs.remove(&wd);
return format_test_result(&result);
}
Ok(None) => {} // still running, keep waiting
Err(e) => {
jobs.remove(&wd);
return Err(format!("Failed to check child status: {e}"));
}
}
} else if let Some(result) = job.result.clone() {
jobs.remove(&wd);
return format_test_result(&result);
}
} else {
return Err(
"No test job running for this worktree. Call run_tests first.".to_string(),
);
}
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
// Still running after blocking period — return status.
let mut jobs = ctx.test_jobs.lock().map_err(|e| e.to_string())?; let mut jobs = ctx.test_jobs.lock().map_err(|e| e.to_string())?;
let job = jobs.get_mut(&working_dir).ok_or_else(|| { let job = jobs.get_mut(&working_dir).ok_or_else(|| {