diff options
| author | soryu <soryu@soryu.co> | 2026-02-16 15:09:52 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-16 15:09:59 +0000 |
| commit | 29ec8e53f2acf56fe4a2cd02d352144c697a6afc (patch) | |
| tree | ae4ad54a3c584fa2e9949f483c9f2b5edd9b819f | |
| parent | b6a29bb563499b2fd6280c742bd2106d66393112 (diff) | |
| download | soryu-29ec8e53f2acf56fe4a2cd02d352144c697a6afc.tar.gz soryu-29ec8e53f2acf56fe4a2cd02d352144c697a6afc.zip | |
Fix bugs with restoring/continuing from tasks
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 65 | ||||
| -rw-r--r-- | makima/src/orchestration/directive.rs | 640 |
2 files changed, 396 insertions, 309 deletions
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 9dc342e..b382507 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -4508,24 +4508,65 @@ impl TaskManagerInner { ); worktree_info } - Err(e) => { - return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), patch restore failed ({})", from_task_id, copy_err, branch_err, e) - ))); + Err(patch_err) => { + tracing::warn!( + task_id = %task_id, + from_task_id = %from_task_id, + error = %patch_err, + "Patch restore failed — falling back to fresh worktree" + ); + let msg = DaemonMessage::task_output( + task_id, + format!("Patch restore failed, starting fresh from {}\n", branch), + false, + ); + let _ = self.ws_tx.send(msg).await; + + self.worktree_manager + .create_worktree(&source_repo, task_id, &task_name, &branch) + .await + .map_err(|e| DaemonError::Task(TaskError::SetupFailed(e.to_string())))? } } } - Err(e) => { - return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), patch decode failed ({})", from_task_id, copy_err, branch_err, e) - ))); + Err(decode_err) => { + tracing::warn!( + task_id = %task_id, + from_task_id = %from_task_id, + error = %decode_err, + "Patch decode failed — falling back to fresh worktree" + ); + let msg = DaemonMessage::task_output( + task_id, + format!("Patch decode failed, starting fresh from {}\n", branch), + false, + ); + let _ = self.ws_tx.send(msg).await; + + self.worktree_manager + .create_worktree(&source_repo, task_id, &task_name, &branch) + .await + .map_err(|e| DaemonError::Task(TaskError::SetupFailed(e.to_string())))? } } } else { - // Step 4: No fallback available - return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), no patch data available", from_task_id, copy_err, branch_err) - ))); + // Step 4: Fall back to fresh worktree from base branch + tracing::warn!( + task_id = %task_id, + from_task_id = %from_task_id, + "All continue_from fallbacks failed — creating fresh worktree from base branch" + ); + let msg = DaemonMessage::task_output( + task_id, + format!("Source task worktree unavailable, starting fresh from {}\n", branch), + false, + ); + let _ = self.ws_tx.send(msg).await; + + self.worktree_manager + .create_worktree(&source_repo, task_id, &task_name, &branch) + .await + .map_err(|e| DaemonError::Task(TaskError::SetupFailed(e.to_string())))? } } } diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 6e0d83d..25aaf1b 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -9,6 +9,8 @@ use sqlx::PgPool; use uuid::Uuid; +use base64::Engine; + use crate::db::models::{CreateTaskRequest, UpdateTaskRequest}; use crate::db::repository; use crate::server::state::{DaemonCommand, SharedState}; @@ -25,11 +27,21 @@ impl DirectiveOrchestrator { /// Run one orchestration tick — called every 15s. pub async fn tick(&mut self) -> Result<(), anyhow::Error> { - self.phase_planning().await?; - self.phase_execution().await?; - self.phase_monitoring().await?; - self.phase_replanning().await?; - self.phase_completion().await?; + if let Err(e) = self.phase_planning().await { + tracing::warn!(error = %e, "Directive phase_planning failed, continuing to next phase"); + } + if let Err(e) = self.phase_execution().await { + tracing::warn!(error = %e, "Directive phase_execution failed, continuing to next phase"); + } + if let Err(e) = self.phase_monitoring().await { + tracing::warn!(error = %e, "Directive phase_monitoring failed, continuing to next phase"); + } + if let Err(e) = self.phase_replanning().await { + tracing::warn!(error = %e, "Directive phase_replanning failed, continuing to next phase"); + } + if let Err(e) = self.phase_completion().await { + tracing::warn!(error = %e, "Directive phase_completion failed"); + } Ok(()) } @@ -82,8 +94,13 @@ impl DirectiveOrchestrator { ); // Resolve dependency steps to their task IDs for worktree continuation - let dep_tasks = - repository::get_step_dependency_tasks(&self.pool, &step.depends_on).await?; + let dep_tasks = match repository::get_step_dependency_tasks(&self.pool, &step.depends_on).await { + Ok(deps) => deps, + Err(e) => { + tracing::warn!(step_id = %step.step_id, error = %e, "Failed to resolve step dependencies — skipping step"); + continue; + } + }; let mut continue_from_task_id = dep_tasks.first().map(|d| d.task_id); // If no dependency tasks resolved, try to continue from previous work: @@ -214,53 +231,54 @@ impl DirectiveOrchestrator { let running = repository::get_running_steps_with_tasks(&self.pool).await?; for step in running { - match step.task_status.as_str() { - "completed" | "merged" | "done" => { - tracing::info!( - step_id = %step.step_id, - directive_id = %step.directive_id, - task_id = %step.task_id, - "Step task completed — updating step to completed" - ); - let update = crate::db::models::UpdateDirectiveStepRequest { - status: Some("completed".to_string()), - ..Default::default() - }; - repository::update_directive_step(&self.pool, step.step_id, update).await?; - repository::advance_directive_ready_steps(&self.pool, step.directive_id) - .await?; - repository::check_directive_idle(&self.pool, step.directive_id).await?; - } - "failed" | "interrupted" => { - tracing::warn!( - step_id = %step.step_id, - directive_id = %step.directive_id, - task_id = %step.task_id, - task_status = %step.task_status, - "Step task failed — updating step to failed" - ); - let update = crate::db::models::UpdateDirectiveStepRequest { - status: Some("failed".to_string()), - ..Default::default() - }; - repository::update_directive_step(&self.pool, step.step_id, update).await?; - repository::advance_directive_ready_steps(&self.pool, step.directive_id) - .await?; - repository::check_directive_idle(&self.pool, step.directive_id).await?; - } - "paused" => { - // Task is paused (e.g., waiting for user answer in reconcile mode) - // Keep step in running status — task will auto-resume when answered - tracing::debug!( - step_id = %step.step_id, - directive_id = %step.directive_id, - task_id = %step.task_id, - "Step task paused (waiting for user response) — keeping step running" - ); - } - _ => { - // Still running — do nothing + if let Err(e) = async { + match step.task_status.as_str() { + "completed" | "merged" | "done" => { + tracing::info!( + step_id = %step.step_id, + directive_id = %step.directive_id, + task_id = %step.task_id, + "Step task completed — updating step to completed" + ); + let update = crate::db::models::UpdateDirectiveStepRequest { + status: Some("completed".to_string()), + ..Default::default() + }; + repository::update_directive_step(&self.pool, step.step_id, update).await?; + repository::advance_directive_ready_steps(&self.pool, step.directive_id) + .await?; + repository::check_directive_idle(&self.pool, step.directive_id).await?; + } + "failed" | "interrupted" => { + tracing::warn!( + step_id = %step.step_id, + directive_id = %step.directive_id, + task_id = %step.task_id, + task_status = %step.task_status, + "Step task failed — updating step to failed" + ); + let update = crate::db::models::UpdateDirectiveStepRequest { + status: Some("failed".to_string()), + ..Default::default() + }; + repository::update_directive_step(&self.pool, step.step_id, update).await?; + repository::advance_directive_ready_steps(&self.pool, step.directive_id) + .await?; + repository::check_directive_idle(&self.pool, step.directive_id).await?; + } + "paused" => { + tracing::debug!( + step_id = %step.step_id, + directive_id = %step.directive_id, + task_id = %step.task_id, + "Step task paused (waiting for user response) — keeping step running" + ); + } + _ => {} } + Ok::<(), anyhow::Error>(()) + }.await { + tracing::warn!(step_id = %step.step_id, error = %e, "Error processing running step — continuing"); } } @@ -268,34 +286,38 @@ impl DirectiveOrchestrator { let orch_tasks = repository::get_orchestrator_tasks_to_check(&self.pool).await?; for orch in orch_tasks { - match orch.task_status.as_str() { - "completed" | "merged" | "done" => { - tracing::info!( - directive_id = %orch.directive_id, - task_id = %orch.orchestrator_task_id, - "Planning task completed — clearing orchestrator task" - ); - repository::clear_orchestrator_task(&self.pool, orch.directive_id).await?; - // Advance DAG — planning task should have created steps - repository::advance_directive_ready_steps(&self.pool, orch.directive_id) + if let Err(e) = async { + match orch.task_status.as_str() { + "completed" | "merged" | "done" => { + tracing::info!( + directive_id = %orch.directive_id, + task_id = %orch.orchestrator_task_id, + "Planning task completed — clearing orchestrator task" + ); + repository::clear_orchestrator_task(&self.pool, orch.directive_id).await?; + repository::advance_directive_ready_steps(&self.pool, orch.directive_id) + .await?; + } + "failed" | "interrupted" => { + tracing::warn!( + directive_id = %orch.directive_id, + task_id = %orch.orchestrator_task_id, + "Planning task failed — pausing directive" + ); + repository::clear_orchestrator_task(&self.pool, orch.directive_id).await?; + repository::set_directive_status( + &self.pool, + orch.owner_id, + orch.directive_id, + "paused", + ) .await?; + } + _ => {} } - "failed" | "interrupted" => { - tracing::warn!( - directive_id = %orch.directive_id, - task_id = %orch.orchestrator_task_id, - "Planning task failed — pausing directive" - ); - repository::clear_orchestrator_task(&self.pool, orch.directive_id).await?; - repository::set_directive_status( - &self.pool, - orch.owner_id, - orch.directive_id, - "paused", - ) - .await?; - } - _ => {} + Ok::<(), anyhow::Error>(()) + }.await { + tracing::warn!(directive_id = %orch.directive_id, error = %e, "Error processing orchestrator task — continuing"); } } @@ -307,38 +329,43 @@ impl DirectiveOrchestrator { let directives = repository::get_directives_needing_replanning(&self.pool).await?; for directive in directives { - tracing::info!( - directive_id = %directive.id, - title = %directive.title, - "Directive goal updated — spawning re-planning task" - ); + if let Err(e) = async { + tracing::info!( + directive_id = %directive.id, + title = %directive.title, + "Directive goal updated — spawning re-planning task" + ); - let existing_steps = - repository::list_directive_steps(&self.pool, directive.id).await?; - let generation = - repository::get_directive_max_generation(&self.pool, directive.id).await? + 1; - let goal_history = - repository::get_directive_goal_history(&self.pool, directive.id, 3).await?; + let existing_steps = + repository::list_directive_steps(&self.pool, directive.id).await?; + let generation = + repository::get_directive_max_generation(&self.pool, directive.id).await? + 1; + let goal_history = + repository::get_directive_goal_history(&self.pool, directive.id, 3).await?; - let plan = - build_planning_prompt(&directive, &existing_steps, generation, &goal_history); + let plan = + build_planning_prompt(&directive, &existing_steps, generation, &goal_history); - if let Err(e) = self - .spawn_orchestrator_task( - directive.id, - directive.owner_id, - format!("Re-plan: {}", directive.title), - plan, - directive.repository_url.as_deref(), - directive.base_branch.as_deref(), - ) - .await - { - tracing::warn!( - directive_id = %directive.id, - error = %e, - "Failed to spawn re-planning task" - ); + if let Err(e) = self + .spawn_orchestrator_task( + directive.id, + directive.owner_id, + format!("Re-plan: {}", directive.title), + plan, + directive.repository_url.as_deref(), + directive.base_branch.as_deref(), + ) + .await + { + tracing::warn!( + directive_id = %directive.id, + error = %e, + "Failed to spawn re-planning task" + ); + } + Ok::<(), anyhow::Error>(()) + }.await { + tracing::warn!(directive_id = %directive.id, error = %e, "Error in re-planning directive — continuing"); } } Ok(()) @@ -479,6 +506,19 @@ impl DirectiveOrchestrator { match repository::update_task_for_owner(&self.pool, task_id, owner_id, update_req).await { Ok(Some(updated_task)) => { + // Load patch data from continue_from task for worktree recovery + let (patch_data, patch_base_sha) = if let Some(from_id) = updated_task.continue_from_task_id { + match repository::get_latest_checkpoint_patch(&self.pool, from_id).await { + Ok(Some(patch)) => { + let encoded = base64::engine::general_purpose::STANDARD.encode(&patch.patch_data); + (Some(encoded), Some(patch.base_commit_sha)) + } + _ => (None, None), + } + } else { + (None, None) + }; + let command = DaemonCommand::SpawnTask { task_id, task_name: task_name.to_string(), @@ -498,8 +538,8 @@ impl DirectiveOrchestrator { autonomous_loop: false, resume_session: false, conversation_history: None, - patch_data: None, - patch_base_sha: None, + patch_data, + patch_base_sha, local_only: false, auto_merge_local: false, supervisor_worktree_task_id: None, @@ -539,104 +579,104 @@ impl DirectiveOrchestrator { let directives = repository::get_idle_directives_needing_completion(&self.pool).await?; for directive in directives { - // Atomically claim this directive for completion using a placeholder. - // This prevents a concurrent tick from also spawning a completion task. - let placeholder_id = Uuid::new_v4(); - let claimed = repository::claim_directive_for_completion( - &self.pool, - directive.id, - placeholder_id, - ) - .await?; - - if !claimed { - tracing::debug!( + if let Err(e) = async { + // Atomically claim this directive for completion using a placeholder. + // This prevents a concurrent tick from also spawning a completion task. + let placeholder_id = Uuid::new_v4(); + let claimed = repository::claim_directive_for_completion( + &self.pool, + directive.id, + placeholder_id, + ) + .await?; + + if !claimed { + tracing::debug!( + directive_id = %directive.id, + "Directive already claimed for completion — skipping" + ); + return Ok::<(), anyhow::Error>(()); + } + + tracing::info!( directive_id = %directive.id, - "Directive already claimed for completion — skipping" + title = %directive.title, + "Directive idle — spawning completion task for PR" ); - continue; - } - tracing::info!( - directive_id = %directive.id, - title = %directive.title, - "Directive idle — spawning completion task for PR" - ); + let step_tasks = repository::get_completed_step_tasks(&self.pool, directive.id).await?; + if step_tasks.is_empty() { + let _ = repository::clear_completion_task(&self.pool, directive.id).await; + return Ok(()); + } - let step_tasks = repository::get_completed_step_tasks(&self.pool, directive.id).await?; - if step_tasks.is_empty() { - // Release the claim since there's nothing to complete - let _ = repository::clear_completion_task(&self.pool, directive.id).await; - continue; - } + let base_branch = directive + .base_branch + .as_deref() + .unwrap_or("main"); - let base_branch = directive - .base_branch - .as_deref() - .unwrap_or("main"); + let directive_branch = format!( + "makima/directive-{}-{}", + crate::daemon::worktree::sanitize_name(&directive.title), + crate::daemon::worktree::short_uuid(directive.id), + ); - let directive_branch = format!( - "makima/directive-{}-{}", - crate::daemon::worktree::sanitize_name(&directive.title), - crate::daemon::worktree::short_uuid(directive.id), - ); + let step_branches: Vec<String> = step_tasks + .iter() + .map(|st| { + format!( + "makima/{}-{}", + crate::daemon::worktree::sanitize_name(&st.task_name), + crate::daemon::worktree::short_uuid(st.task_id), + ) + }) + .collect(); - // Compute step branch names using the same formula as execute_completion_action - let step_branches: Vec<String> = step_tasks - .iter() - .map(|st| { - format!( - "makima/{}-{}", - crate::daemon::worktree::sanitize_name(&st.task_name), - crate::daemon::worktree::short_uuid(st.task_id), - ) - }) - .collect(); - - let prompt = build_completion_prompt( - &directive, - &step_tasks, - &step_branches, - &directive_branch, - base_branch, - ); + let prompt = build_completion_prompt( + &directive, + &step_tasks, + &step_branches, + &directive_branch, + base_branch, + ); - match self - .spawn_completion_task( - directive.id, - directive.owner_id, - format!("PR: {}", directive.title), - prompt, - directive.repository_url.as_deref(), - directive.base_branch.as_deref(), - ) - .await - { - Ok(task_id) => { - // Store pr_branch on directive immediately - let update = crate::db::models::UpdateDirectiveRequest { - pr_branch: Some(directive_branch.clone()), - ..Default::default() - }; - let _ = repository::update_directive_for_owner( - &self.pool, - directive.owner_id, + match self + .spawn_completion_task( directive.id, - update, + directive.owner_id, + format!("PR: {}", directive.title), + prompt, + directive.repository_url.as_deref(), + directive.base_branch.as_deref(), ) - .await; - // Replace placeholder with the real task ID - repository::assign_completion_task(&self.pool, directive.id, task_id).await?; - } - Err(e) => { - tracing::warn!( - directive_id = %directive.id, - error = %e, - "Failed to spawn completion task — releasing claim" - ); - // Release the claim so it can be retried on the next tick - let _ = repository::clear_completion_task(&self.pool, directive.id).await; + .await + { + Ok(task_id) => { + let update = crate::db::models::UpdateDirectiveRequest { + pr_branch: Some(directive_branch.clone()), + ..Default::default() + }; + let _ = repository::update_directive_for_owner( + &self.pool, + directive.owner_id, + directive.id, + update, + ) + .await; + repository::assign_completion_task(&self.pool, directive.id, task_id).await?; + } + Err(e) => { + tracing::warn!( + directive_id = %directive.id, + error = %e, + "Failed to spawn completion task — releasing claim" + ); + let _ = repository::clear_completion_task(&self.pool, directive.id).await; + } } + Ok(()) + }.await { + tracing::warn!(directive_id = %directive.id, error = %e, "Error processing directive completion — continuing"); } } @@ -644,45 +684,25 @@ impl DirectiveOrchestrator { let checks = repository::get_completion_tasks_to_check(&self.pool).await?; for check in checks { - match check.task_status.as_str() { - "completed" | "merged" | "done" => { - tracing::info!( - directive_id = %check.directive_id, - task_id = %check.completion_task_id, - "Completion task finished" - ); + if let Err(e) = async { + match check.task_status.as_str() { + "completed" | "merged" | "done" => { + tracing::info!( + directive_id = %check.directive_id, + task_id = %check.completion_task_id, + "Completion task finished" + ); - // If directive has no pr_url yet, try to extract from task output - if check.pr_url.is_none() { - match self.extract_pr_url_from_task(check.completion_task_id).await { - Ok(Some(url)) => { - tracing::info!( - directive_id = %check.directive_id, - pr_url = %url, - "Extracted PR URL from completion task output" - ); - let update = crate::db::models::UpdateDirectiveRequest { - pr_url: Some(url), - ..Default::default() - }; - let _ = repository::update_directive_for_owner( - &self.pool, - check.owner_id, - check.directive_id, - update, - ) - .await; - } - Ok(None) => { - if check.task_name.starts_with("Verify PR:") { - // Verification task failed to find/create PR — mark directive completed (one-shot) - tracing::warn!( + if check.pr_url.is_none() { + match self.extract_pr_url_from_task(check.completion_task_id).await { + Ok(Some(url)) => { + tracing::info!( directive_id = %check.directive_id, - task_id = %check.completion_task_id, - "Verification task finished but no PR URL found — marking directive completed" + pr_url = %url, + "Extracted PR URL from completion task output" ); let update = crate::db::models::UpdateDirectiveRequest { - status: Some("completed".to_string()), + pr_url: Some(url), ..Default::default() }; let _ = repository::update_directive_for_owner( @@ -692,37 +712,58 @@ impl DirectiveOrchestrator { update, ) .await; - } else { + } + Ok(None) => { + if check.task_name.starts_with("Verify PR:") { + tracing::warn!( + directive_id = %check.directive_id, + task_id = %check.completion_task_id, + "Verification task finished but no PR URL found — marking directive completed" + ); + let update = crate::db::models::UpdateDirectiveRequest { + status: Some("archived".to_string()), + ..Default::default() + }; + let _ = repository::update_directive_for_owner( + &self.pool, + check.owner_id, + check.directive_id, + update, + ) + .await; + } else { + tracing::warn!( + directive_id = %check.directive_id, + task_id = %check.completion_task_id, + "Completion task finished but no PR URL found — will spawn verifier" + ); + } + } + Err(e) => { tracing::warn!( directive_id = %check.directive_id, - task_id = %check.completion_task_id, - "Completion task finished but no PR URL found — will spawn verifier" + error = %e, + "Failed to extract PR URL from completion task output" ); } } - Err(e) => { - tracing::warn!( - directive_id = %check.directive_id, - error = %e, - "Failed to extract PR URL from completion task output" - ); - } } - } - repository::clear_completion_task(&self.pool, check.directive_id).await?; - } - "failed" | "interrupted" => { - tracing::warn!( - directive_id = %check.directive_id, - task_id = %check.completion_task_id, - "Completion task failed" - ); - repository::clear_completion_task(&self.pool, check.directive_id).await?; - } - _ => { - // Still running + repository::clear_completion_task(&self.pool, check.directive_id).await?; + } + "failed" | "interrupted" => { + tracing::warn!( + directive_id = %check.directive_id, + task_id = %check.completion_task_id, + "Completion task failed" + ); + repository::clear_completion_task(&self.pool, check.directive_id).await?; + } + _ => {} } + Ok::<(), anyhow::Error>(()) + }.await { + tracing::warn!(directive_id = %check.directive_id, error = %e, "Error processing completion task — continuing"); } } @@ -731,50 +772,55 @@ impl DirectiveOrchestrator { repository::get_directives_needing_verification(&self.pool).await?; for directive in verify_directives { - let placeholder_id = Uuid::new_v4(); - let claimed = repository::claim_directive_for_completion( - &self.pool, - directive.id, - placeholder_id, - ) - .await?; - - if !claimed { - continue; - } - - tracing::info!( - directive_id = %directive.id, - title = %directive.title, - "Directive has pr_branch but no pr_url — spawning verification task" - ); - - let pr_branch = directive.pr_branch.as_deref().unwrap_or("unknown"); - let base_branch = directive.base_branch.as_deref().unwrap_or("main"); - let prompt = build_verification_prompt(&directive, pr_branch, base_branch); - - match self - .spawn_completion_task( + if let Err(e) = async { + let placeholder_id = Uuid::new_v4(); + let claimed = repository::claim_directive_for_completion( + &self.pool, directive.id, - directive.owner_id, - format!("Verify PR: {}", directive.title), - prompt, - directive.repository_url.as_deref(), - directive.base_branch.as_deref(), + placeholder_id, ) - .await - { - Ok(task_id) => { - repository::assign_completion_task(&self.pool, directive.id, task_id).await?; + .await?; + + if !claimed { + return Ok::<(), anyhow::Error>(()); } - Err(e) => { - tracing::warn!( - directive_id = %directive.id, - error = %e, - "Failed to spawn verification task — releasing claim" - ); - let _ = repository::clear_completion_task(&self.pool, directive.id).await; + + tracing::info!( + directive_id = %directive.id, + title = %directive.title, + "Directive has pr_branch but no pr_url — spawning verification task" + ); + + let pr_branch = directive.pr_branch.as_deref().unwrap_or("unknown"); + let base_branch = directive.base_branch.as_deref().unwrap_or("main"); + let prompt = build_verification_prompt(&directive, pr_branch, base_branch); + + match self + .spawn_completion_task( + directive.id, + directive.owner_id, + format!("Verify PR: {}", directive.title), + prompt, + directive.repository_url.as_deref(), + directive.base_branch.as_deref(), + ) + .await + { + Ok(task_id) => { + repository::assign_completion_task(&self.pool, directive.id, task_id).await?; + } + Err(e) => { + tracing::warn!( + directive_id = %directive.id, + error = %e, + "Failed to spawn verification task — releasing claim" + ); + let _ = repository::clear_completion_task(&self.pool, directive.id).await; + } } + Ok(()) + }.await { + tracing::warn!(directive_id = %directive.id, error = %e, "Error processing verification directive — continuing"); } } @@ -1222,10 +1268,10 @@ makima directive update --pr-url "<URL_FROM_GH_PR_CREATE>" 5. If the branch does NOT exist on the remote, the work was likely merged directly. Mark the directive as completed: ``` -makima directive update --status completed +makima directive update --status archived ``` -IMPORTANT: You MUST run `makima directive update` with either `--pr-url` or `--status completed` before finishing. +IMPORTANT: You MUST run `makima directive update` with either `--pr-url` or `--status archived` before finishing. "#, title = directive.title, pr_branch = pr_branch, |
