From 264e0f9d6a26b94902f40fd97d9077d54f19f9e5 Mon Sep 17 00:00:00 2001 From: Zaki Date: Sat, 7 Mar 2026 19:03:02 -0800 Subject: [PATCH 1/8] feat(self-repair): wire stuck_threshold, store, and builder (#647) Wire the previously dead-code fields in DefaultSelfRepair: - stuck_threshold: detect_stuck_jobs() now filters by duration, only reporting jobs stuck longer than the configured threshold - with_store(): wired in agent_loop.rs from AgentDeps.store for tool failure tracking via Database trait - with_builder(): wired from register_builder_tool() return value through AppComponents and AgentDeps for automatic tool rebuilding - tools: passed alongside builder for hot-reload logging Remove all #[allow(dead_code)] annotations. Add regression tests for threshold-based filtering (both above and below threshold). Co-Authored-By: Claude Opus 4.6 --- src/agent/agent_loop.rs | 13 ++++++- src/agent/dispatcher.rs | 3 ++ src/agent/self_repair.rs | 78 +++++++++++++++++++++++++++++++++------ src/app.rs | 18 ++++++--- src/main.rs | 1 + src/testing/mod.rs | 1 + src/tools/registry.rs | 13 ++++--- tests/support/test_rig.rs | 1 + 8 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index aaaad879d1..d924f1ee91 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -97,6 +97,8 @@ pub struct AgentDeps { pub transcription: Option>, /// Document text extraction middleware for PDF, DOCX, PPTX, etc. pub document_extraction: Option>, + /// Software builder for self-repair tool rebuilding. + pub builder: Option>, } /// The main agent that coordinates all components. @@ -285,11 +287,18 @@ impl Agent { let mut message_stream = self.channels.start_all().await?; // Start self-repair task with notification forwarding - let repair = Arc::new(DefaultSelfRepair::new( + let mut self_repair = DefaultSelfRepair::new( self.context_manager.clone(), self.config.stuck_threshold, self.config.max_repair_attempts, - )); + ); + if let Some(ref store) = self.deps.store { + self_repair = self_repair.with_store(Arc::clone(store)); + } + if let Some(ref builder) = self.deps.builder { + self_repair = self_repair.with_builder(Arc::clone(builder), Arc::clone(self.tools())); + } + let repair = Arc::new(self_repair); let repair_interval = self.config.repair_check_interval; let repair_channels = self.channels.clone(); let repair_owner_id = self.owner_id().to_string(); diff --git a/src/agent/dispatcher.rs b/src/agent/dispatcher.rs index 9be0d654d1..49387e8351 100644 --- a/src/agent/dispatcher.rs +++ b/src/agent/dispatcher.rs @@ -1197,6 +1197,7 @@ mod tests { http_interceptor: None, transcription: None, document_extraction: None, + builder: None, }; Agent::new( @@ -2037,6 +2038,7 @@ mod tests { http_interceptor: None, transcription: None, document_extraction: None, + builder: None, }; Agent::new( @@ -2155,6 +2157,7 @@ mod tests { http_interceptor: None, transcription: None, document_extraction: None, + builder: None, }; Agent::new( diff --git a/src/agent/self_repair.rs b/src/agent/self_repair.rs index a67fe23eba..a99a44a32e 100644 --- a/src/agent/self_repair.rs +++ b/src/agent/self_repair.rs @@ -66,14 +66,10 @@ pub trait SelfRepair: Send + Sync { /// Default self-repair implementation. pub struct DefaultSelfRepair { context_manager: Arc, - // TODO: use for time-based stuck detection (currently only max_repair_attempts is checked) - #[allow(dead_code)] stuck_threshold: Duration, max_repair_attempts: u32, store: Option>, builder: Option>, - // TODO: use for tool hot-reload after repair - #[allow(dead_code)] tools: Option>, } @@ -95,15 +91,13 @@ impl DefaultSelfRepair { } /// Add a Store for tool failure tracking. - #[allow(dead_code)] // TODO: wire up in main.rs when persistence is needed - pub(crate) fn with_store(mut self, store: Arc) -> Self { + pub fn with_store(mut self, store: Arc) -> Self { self.store = Some(store); self } /// Add a Builder and ToolRegistry for automatic tool repair. - #[allow(dead_code)] // TODO: wire up in main.rs when auto-repair is needed - pub(crate) fn with_builder( + pub fn with_builder( mut self, builder: Arc, tools: Arc, @@ -133,6 +127,11 @@ impl SelfRepair for DefaultSelfRepair { }) .unwrap_or_default(); + // Only report jobs that have been stuck long enough + if stuck_duration < self.stuck_threshold { + continue; + } + stuck_jobs.push(StuckJob { job_id, last_activity: ctx.started_at.unwrap_or(ctx.created_at), @@ -273,9 +272,14 @@ impl SelfRepair for DefaultSelfRepair { tracing::warn!("Failed to mark tool as repaired: {}", e); } - // Log if the tool was auto-registered + // Hot-reload: if the tool was registered by the builder and + // we have access to the registry, log the reload. if result.registered { - tracing::info!("Repaired tool '{}' auto-registered", tool.name); + if self.tools.is_some() { + tracing::info!("Repaired tool '{}' hot-reloaded into registry", tool.name); + } else { + tracing::info!("Repaired tool '{}' auto-registered", tool.name); + } } Ok(RepairResult::Success { @@ -417,7 +421,8 @@ mod tests { .unwrap() .unwrap(); - let repair = DefaultSelfRepair::new(cm, Duration::from_secs(60), 3); + // Use zero threshold so the just-stuck job is detected immediately. + let repair = DefaultSelfRepair::new(cm, Duration::from_secs(0), 3); let stuck = repair.detect_stuck_jobs().await; assert_eq!(stuck.len(), 1); assert_eq!(stuck[0].job_id, job_id); @@ -483,6 +488,57 @@ mod tests { ); } + #[tokio::test] + async fn detect_stuck_jobs_filters_by_threshold() { + let cm = Arc::new(ContextManager::new(10)); + let job_id = cm.create_job("Stuck job", "desc").await.unwrap(); + + // Transition to InProgress, then to Stuck. + cm.update_context(job_id, |ctx| ctx.transition_to(JobState::InProgress, None)) + .await + .unwrap() + .unwrap(); + cm.update_context(job_id, |ctx| { + ctx.transition_to(JobState::Stuck, Some("timed out".to_string())) + }) + .await + .unwrap() + .unwrap(); + + // Use a very large threshold (1 hour). Job just became stuck, so + // stuck_duration < threshold. It should be filtered out. + let repair = DefaultSelfRepair::new(cm, Duration::from_secs(3600), 3); + let stuck = repair.detect_stuck_jobs().await; + assert!( + stuck.is_empty(), + "Job stuck for <1s should be filtered by 1h threshold" + ); + } + + #[tokio::test] + async fn detect_stuck_jobs_includes_when_over_threshold() { + let cm = Arc::new(ContextManager::new(10)); + let job_id = cm.create_job("Stuck job", "desc").await.unwrap(); + + // Transition to InProgress, then to Stuck. + cm.update_context(job_id, |ctx| ctx.transition_to(JobState::InProgress, None)) + .await + .unwrap() + .unwrap(); + cm.update_context(job_id, |ctx| { + ctx.transition_to(JobState::Stuck, Some("timed out".to_string())) + }) + .await + .unwrap() + .unwrap(); + + // Use a zero threshold -- any stuck duration should be included. + let repair = DefaultSelfRepair::new(cm, Duration::from_secs(0), 3); + let stuck = repair.detect_stuck_jobs().await; + assert_eq!(stuck.len(), 1, "Job should be detected with zero threshold"); + assert_eq!(stuck[0].job_id, job_id); + } + #[tokio::test] async fn detect_broken_tools_returns_empty_without_store() { let cm = Arc::new(ContextManager::new(10)); diff --git a/src/app.rs b/src/app.rs index 0ffe782064..fa6675bfad 100644 --- a/src/app.rs +++ b/src/app.rs @@ -56,6 +56,7 @@ pub struct AppComponents { pub session: Arc, pub catalog_entries: Vec, pub dev_loaded_tool_names: Vec, + pub builder: Option>, } /// Options that control optional init phases. @@ -280,6 +281,7 @@ impl AppBuilder { Arc, Option>, Option>, + Option>, ), anyhow::Error, > { @@ -367,16 +369,19 @@ impl AppBuilder { } // Register builder tool if enabled - if self.config.builder.enabled + let builder = if self.config.builder.enabled && (self.config.agent.allow_local_tools || !self.config.sandbox.enabled) { - tools + let b = tools .register_builder_tool(llm.clone(), Some(self.config.builder.to_builder_config())) .await; - tracing::debug!("Builder mode enabled"); - } + tracing::info!("Builder mode enabled"); + Some(b) + } else { + None + }; - Ok((safety, tools, embeddings, workspace)) + Ok((safety, tools, embeddings, workspace, builder)) } /// Phase 5: Load WASM tools, MCP servers, and create extension manager. @@ -699,7 +704,7 @@ impl AppBuilder { } else { self.init_llm().await? }; - let (safety, tools, embeddings, workspace) = self.init_tools(&llm).await?; + let (safety, tools, embeddings, workspace, builder) = self.init_tools(&llm).await?; // Create hook registry early so runtime extension activation can register hooks. let hooks = Arc::new(HookRegistry::new()); @@ -819,6 +824,7 @@ impl AppBuilder { session: self.session, catalog_entries, dev_loaded_tool_names, + builder, }) } } diff --git a/src/main.rs b/src/main.rs index ae864bed9b..3a098395a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -727,6 +727,7 @@ async fn async_main() -> anyhow::Result<()> { document_extraction: Some(Arc::new( ironclaw::document_extraction::DocumentExtractionMiddleware::new(), )), + builder: components.builder, }; let mut agent = Agent::new( diff --git a/src/testing/mod.rs b/src/testing/mod.rs index ff522e3ad2..4f51e321f2 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -456,6 +456,7 @@ impl TestHarnessBuilder { http_interceptor: None, transcription: None, document_extraction: None, + builder: None, }; TestHarness { diff --git a/src/tools/registry.rs b/src/tools/registry.rs index 754869c8cf..a419b19571 100644 --- a/src/tools/registry.rs +++ b/src/tools/registry.rs @@ -13,7 +13,9 @@ use crate::orchestrator::job_manager::ContainerJobManager; use crate::secrets::SecretsStore; use crate::skills::catalog::SkillCatalog; use crate::skills::registry::SkillRegistry; -use crate::tools::builder::{BuildSoftwareTool, BuilderConfig, LlmSoftwareBuilder}; +use crate::tools::builder::{ + BuildSoftwareTool, BuilderConfig, LlmSoftwareBuilder, SoftwareBuilder, +}; use crate::tools::builtin::{ ApplyPatchTool, CancelJobTool, CreateJobTool, EchoTool, ExtensionInfoTool, HttpTool, JobEventsTool, JobPromptTool, JobStatusTool, JsonTool, ListDirTool, ListJobsTool, @@ -580,22 +582,23 @@ impl ToolRegistry { self: &Arc, llm: Arc, config: Option, - ) { + ) -> Arc { // First register dev tools needed by the builder self.register_dev_tools(); // Create the builder (arg order: config, llm, tools) - let builder = Arc::new(LlmSoftwareBuilder::new( + let builder: Arc = Arc::new(LlmSoftwareBuilder::new( config.unwrap_or_default(), llm, Arc::clone(self), )); // Register the build_software tool - self.register(Arc::new(BuildSoftwareTool::new(builder))) + self.register(Arc::new(BuildSoftwareTool::new(Arc::clone(&builder)))) .await; - tracing::debug!("Registered software builder tool"); + tracing::info!("Registered software builder tool"); + builder } /// Register a WASM tool from bytes. diff --git a/tests/support/test_rig.rs b/tests/support/test_rig.rs index 8549a21cb1..7adc31a57c 100644 --- a/tests/support/test_rig.rs +++ b/tests/support/test_rig.rs @@ -642,6 +642,7 @@ impl TestRigBuilder { }, transcription: None, document_extraction: None, + builder: None, }; // 7. Create TestChannel and ChannelManager. From 10dff18aaef3a42791a87943bd49c81d2391276a Mon Sep 17 00:00:00 2001 From: Zaki Date: Thu, 12 Mar 2026 15:04:36 -0700 Subject: [PATCH 2/8] fix: add missing `builder` field to AgentDeps in gateway workflow harness After rebase onto staging, AgentDeps gained a `builder` field for self-repair tool rebuilding. The gateway workflow test harness was missing this field, causing CI compilation failure. Co-Authored-By: Claude Opus 4.6 --- tests/support/gateway_workflow_harness.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/support/gateway_workflow_harness.rs b/tests/support/gateway_workflow_harness.rs index a4d737b52a..1751faa542 100644 --- a/tests/support/gateway_workflow_harness.rs +++ b/tests/support/gateway_workflow_harness.rs @@ -256,6 +256,7 @@ impl GatewayWorkflowHarness { http_interceptor: None, transcription: None, document_extraction: None, + builder: None, }, channels, None, From bd06d765e1dbe7b1634460d29c40cd01b84b18f7 Mon Sep 17 00:00:00 2001 From: Zaki Date: Thu, 12 Mar 2026 15:14:41 -0700 Subject: [PATCH 3/8] ci: retrigger CI From ecd8313a9ebd596dffaef8417cac75822ed30ac7 Mon Sep 17 00:00:00 2001 From: Zaki Date: Thu, 12 Mar 2026 15:23:01 -0700 Subject: [PATCH 4/8] fix: force CI refresh after path_routing_tests dedup --- src/tools/builtin/memory.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/builtin/memory.rs b/src/tools/builtin/memory.rs index f1f846843c..334613029c 100644 --- a/src/tools/builtin/memory.rs +++ b/src/tools/builtin/memory.rs @@ -636,3 +636,4 @@ mod tests { } } } +// ci fix From 79a970fa703b8dbf6698ea9a11c046ab28142381 Mon Sep 17 00:00:00 2001 From: Zaki Date: Sun, 15 Mar 2026 23:00:08 -0700 Subject: [PATCH 5/8] test: add E2E test for stuck job repair and tool rebuild cycle Tests the full self-repair flow requested in review: 1. Job transitions Pending -> InProgress -> Stuck 2. detect_stuck_jobs() finds it (zero threshold) 3. repair_stuck_job() recovers it back to InProgress 4. A broken tool is repaired via MockBuilder 5. Verify builder was invoked and repair succeeded Uses a MockBuilder (impl SoftwareBuilder) that returns successful BuildResult without requiring an LLM or filesystem. Uses libsql test database for the store (increment_repair_attempts, mark_tool_repaired). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/agent/self_repair.rs | 144 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/agent/self_repair.rs b/src/agent/self_repair.rs index a99a44a32e..fe45ead539 100644 --- a/src/agent/self_repair.rs +++ b/src/agent/self_repair.rs @@ -571,4 +571,148 @@ mod tests { result ); } + + /// Mock SoftwareBuilder that returns a successful build result. + struct MockBuilder { + build_count: std::sync::atomic::AtomicU32, + } + + impl MockBuilder { + fn new() -> Self { + Self { + build_count: std::sync::atomic::AtomicU32::new(0), + } + } + + fn builds(&self) -> u32 { + self.build_count.load(std::sync::atomic::Ordering::Relaxed) + } + } + + #[async_trait] + impl crate::tools::SoftwareBuilder for MockBuilder { + async fn analyze( + &self, + _description: &str, + ) -> Result { + Ok(crate::tools::BuildRequirement { + name: "mock-tool".to_string(), + description: "mock".to_string(), + software_type: crate::tools::SoftwareType::WasmTool, + language: crate::tools::Language::Rust, + input_spec: None, + output_spec: None, + dependencies: vec![], + capabilities: vec![], + }) + } + + async fn build( + &self, + requirement: &crate::tools::BuildRequirement, + ) -> Result { + self.build_count + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + Ok(crate::tools::BuildResult { + build_id: Uuid::new_v4(), + requirement: requirement.clone(), + artifact_path: std::path::PathBuf::from("/tmp/mock.wasm"), + logs: vec![], + success: true, + error: None, + started_at: Utc::now(), + completed_at: Utc::now(), + iterations: 1, + validation_warnings: vec![], + tests_passed: 1, + tests_failed: 0, + registered: true, + }) + } + + async fn repair( + &self, + _result: &crate::tools::BuildResult, + _error: &str, + ) -> Result { + unimplemented!("not needed for this test") + } + } + + /// E2E test: stuck job detected -> repaired -> transitions back to InProgress, + /// and broken tool detected -> builder invoked -> tool marked repaired. + #[cfg(feature = "libsql")] + #[tokio::test] + async fn e2e_stuck_job_repair_and_tool_rebuild() { + // --- Setup --- + let cm = Arc::new(ContextManager::new(10)); + let job_id = cm.create_job("E2E stuck job", "desc").await.unwrap(); + + // Transition job: Pending -> InProgress -> Stuck + cm.update_context(job_id, |ctx| ctx.transition_to(JobState::InProgress, None)) + .await + .unwrap() + .unwrap(); + cm.update_context(job_id, |ctx| { + ctx.transition_to(JobState::Stuck, Some("deadlocked".to_string())) + }) + .await + .unwrap() + .unwrap(); + + // Create a mock builder and a real test database (for store) + let builder = Arc::new(MockBuilder::new()); + let tools = Arc::new(ToolRegistry::new()); + let (db, _tmp_dir) = crate::testing::test_db().await; + + // Create self-repair with zero threshold (detect immediately), + // wired with store, builder, and tools. + let repair = DefaultSelfRepair::new(Arc::clone(&cm), Duration::from_secs(0), 3) + .with_store(Arc::clone(&db)) + .with_builder( + Arc::clone(&builder) as Arc, + tools, + ); + + // --- Phase 1: Detect and repair stuck job --- + let stuck_jobs = repair.detect_stuck_jobs().await; + assert_eq!(stuck_jobs.len(), 1, "Should detect the stuck job"); + assert_eq!(stuck_jobs[0].job_id, job_id); + + let result = repair.repair_stuck_job(&stuck_jobs[0]).await.unwrap(); + assert!( + matches!(result, RepairResult::Success { .. }), + "Job repair should succeed: {:?}", + result + ); + + // Verify job transitioned back to InProgress + let ctx = cm.get_context(job_id).await.unwrap(); + assert_eq!( + ctx.state, + JobState::InProgress, + "Job should be back to InProgress after repair" + ); + + // --- Phase 2: Repair a broken tool via builder --- + let broken = BrokenTool { + name: "broken-wasm-tool".to_string(), + failure_count: 10, + last_error: Some("panic in tool execution".to_string()), + first_failure: Utc::now() - chrono::Duration::hours(1), + last_failure: Utc::now(), + last_build_result: None, + repair_attempts: 0, + }; + + let tool_result = repair.repair_broken_tool(&broken).await.unwrap(); + assert!( + matches!(tool_result, RepairResult::Success { .. }), + "Tool repair should succeed with mock builder: {:?}", + tool_result + ); + + // Verify builder was actually invoked + assert_eq!(builder.builds(), 1, "Builder should have been called once"); + } } From 8173dcec9dc43a79b30e0ec246b8853c0ab56f66 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Tue, 17 Mar 2026 00:18:36 +0000 Subject: [PATCH 6/8] fix(self-repair): measure stuck_duration from Stuck transition, not started_at - Use ctx.transitions to find the most recent Stuck transition timestamp instead of ctx.started_at (which reflects job start, not stuck time) - Fix StuckJob.last_activity to use stuck transition timestamp - Remove misleading "hot-reloaded into registry" log - Remove stray "// ci fix" comment in memory.rs - Add regression test: backdated started_at must not inflate stuck_duration Co-Authored-By: Claude Opus 4.6 --- src/agent/self_repair.rs | 68 ++++++++++++++++++++++++++++++------- src/tools/builtin/memory.rs | 1 - 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/agent/self_repair.rs b/src/agent/self_repair.rs index fe45ead539..756b3d5134 100644 --- a/src/agent/self_repair.rs +++ b/src/agent/self_repair.rs @@ -118,11 +118,18 @@ impl SelfRepair for DefaultSelfRepair { if let Ok(ctx) = self.context_manager.get_context(job_id).await && ctx.state == JobState::Stuck { - let stuck_duration = ctx - .started_at - .map(|start| { - let now = Utc::now(); - let duration = now.signed_duration_since(start); + // Measure stuck_duration from the most recent Stuck transition, + // not from started_at (which reflects when the job first ran). + let stuck_since = ctx + .transitions + .iter() + .rev() + .find(|t| t.to == JobState::Stuck) + .map(|t| t.timestamp); + + let stuck_duration = stuck_since + .map(|ts| { + let duration = Utc::now().signed_duration_since(ts); Duration::from_secs(duration.num_seconds().max(0) as u64) }) .unwrap_or_default(); @@ -134,7 +141,7 @@ impl SelfRepair for DefaultSelfRepair { stuck_jobs.push(StuckJob { job_id, - last_activity: ctx.started_at.unwrap_or(ctx.created_at), + last_activity: stuck_since.unwrap_or(ctx.created_at), stuck_duration, last_error: None, repair_attempts: ctx.repair_attempts, @@ -272,14 +279,8 @@ impl SelfRepair for DefaultSelfRepair { tracing::warn!("Failed to mark tool as repaired: {}", e); } - // Hot-reload: if the tool was registered by the builder and - // we have access to the registry, log the reload. if result.registered { - if self.tools.is_some() { - tracing::info!("Repaired tool '{}' hot-reloaded into registry", tool.name); - } else { - tracing::info!("Repaired tool '{}' auto-registered", tool.name); - } + tracing::info!("Repaired tool '{}' auto-registered by builder", tool.name); } Ok(RepairResult::Success { @@ -539,6 +540,47 @@ mod tests { assert_eq!(stuck[0].job_id, job_id); } + /// Regression: stuck_duration must be measured from the Stuck transition, + /// not from started_at. A job that ran for 2 hours before becoming stuck + /// should NOT immediately exceed a 5-minute threshold. + #[tokio::test] + async fn stuck_duration_measured_from_stuck_transition_not_started_at() { + let cm = Arc::new(ContextManager::new(10)); + let job_id = cm.create_job("Long runner", "desc").await.unwrap(); + + // Transition to InProgress (sets started_at to now). + cm.update_context(job_id, |ctx| ctx.transition_to(JobState::InProgress, None)) + .await + .unwrap() + .unwrap(); + + // Backdate started_at to 2 hours ago to simulate a long-running job. + cm.update_context(job_id, |ctx| { + ctx.started_at = Some(Utc::now() - chrono::Duration::hours(2)); + Ok(()) + }) + .await + .unwrap() + .unwrap(); + + // Now transition to Stuck (stuck transition timestamp is ~now). + cm.update_context(job_id, |ctx| { + ctx.transition_to(JobState::Stuck, Some("wedged".into())) + }) + .await + .unwrap() + .unwrap(); + + // With a 5-minute threshold, the job JUST became stuck — should NOT be detected. + let repair = DefaultSelfRepair::new(cm, Duration::from_secs(300), 3); + let stuck = repair.detect_stuck_jobs().await; + assert!( + stuck.is_empty(), + "Job stuck for <1s should not exceed 5min threshold, \ + but stuck_duration was computed from started_at (2h ago)" + ); + } + #[tokio::test] async fn detect_broken_tools_returns_empty_without_store() { let cm = Arc::new(ContextManager::new(10)); diff --git a/src/tools/builtin/memory.rs b/src/tools/builtin/memory.rs index 334613029c..f1f846843c 100644 --- a/src/tools/builtin/memory.rs +++ b/src/tools/builtin/memory.rs @@ -636,4 +636,3 @@ mod tests { } } } -// ci fix From d08294b1c5be0d2e970b4850c812b6e43052d218 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Tue, 17 Mar 2026 02:43:51 +0000 Subject: [PATCH 7/8] ci: re-trigger CI with latest changes Co-Authored-By: Claude Opus 4.6 From fdd1257eb5ededaf27aa6c3d87e51106476b135e Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Tue, 17 Mar 2026 03:05:08 +0000 Subject: [PATCH 8/8] fix: add type annotation to Ok(()) in test to resolve E0282 Co-Authored-By: Claude Opus 4.6 --- src/agent/self_repair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent/self_repair.rs b/src/agent/self_repair.rs index 756b3d5134..db491194f8 100644 --- a/src/agent/self_repair.rs +++ b/src/agent/self_repair.rs @@ -557,7 +557,7 @@ mod tests { // Backdate started_at to 2 hours ago to simulate a long-running job. cm.update_context(job_id, |ctx| { ctx.started_at = Some(Utc::now() - chrono::Duration::hours(2)); - Ok(()) + Ok::<(), crate::error::Error>(()) }) .await .unwrap()