diff options
| author | soryu <soryu@soryu.co> | 2026-01-29 02:30:16 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-29 02:30:16 +0000 |
| commit | 7af8561677cfdcfd23d099a25783c7fef51d1ba6 (patch) | |
| tree | a15cddfa7e5492c3d883419e60a1dad3c9d2d0f3 | |
| parent | cfe3ea0aae878ae8f591acdc33a48332ac875b9e (diff) | |
| download | soryu-7af8561677cfdcfd23d099a25783c7fef51d1ba6.tar.gz soryu-7af8561677cfdcfd23d099a25783c7fef51d1ba6.zip | |
Fix worktree cleanup to not run for shared worktrees
| -rw-r--r-- | makima/migrations/20260129000000_add_supervisor_worktree_task_id.sql | 7 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 23 | ||||
| -rw-r--r-- | makima/src/db/models.rs | 3 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 15 | ||||
| -rw-r--r-- | makima/src/server/handlers/contract_chat.rs | 4 | ||||
| -rw-r--r-- | makima/src/server/handlers/contracts.rs | 13 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh.rs | 4 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_chat.rs | 1 | ||||
| -rw-r--r-- | makima/src/server/handlers/mesh_supervisor.rs | 5 | ||||
| -rw-r--r-- | makima/src/server/handlers/transcript_analysis.rs | 2 |
10 files changed, 68 insertions, 9 deletions
diff --git a/makima/migrations/20260129000000_add_supervisor_worktree_task_id.sql b/makima/migrations/20260129000000_add_supervisor_worktree_task_id.sql new file mode 100644 index 0000000..974644c --- /dev/null +++ b/makima/migrations/20260129000000_add_supervisor_worktree_task_id.sql @@ -0,0 +1,7 @@ +-- Add supervisor_worktree_task_id column to tasks table. +-- When set, indicates this task shares the worktree of the specified supervisor task +-- and should NOT have its worktree deleted during cleanup (the supervisor owns the worktree). +ALTER TABLE tasks ADD COLUMN supervisor_worktree_task_id UUID REFERENCES tasks(id) ON DELETE SET NULL; + +-- Create index for efficient lookups +CREATE INDEX idx_tasks_supervisor_worktree_task_id ON tasks(supervisor_worktree_task_id) WHERE supervisor_worktree_task_id IS NOT NULL; diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 8c5f8d7..1e05978 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -3305,12 +3305,27 @@ impl TaskManager { task_id: Uuid, ) -> Result<(), DaemonError> { // Get task's worktree path and branch + // If the task shares a supervisor's worktree, use the supervisor's worktree info let task_info = { let tasks = self.tasks.read().await; - tasks.get(&task_id).map(|t| ( - t.worktree.as_ref().map(|w| w.path.clone()), - t.worktree.as_ref().map(|w| w.branch.clone()), - )) + if let Some(task) = tasks.get(&task_id) { + // Check if this task shares a supervisor's worktree + if let Some(supervisor_task_id) = task.supervisor_worktree_task_id { + // Use the supervisor's worktree + tasks.get(&supervisor_task_id).map(|supervisor| ( + supervisor.worktree.as_ref().map(|w| w.path.clone()), + supervisor.worktree.as_ref().map(|w| w.branch.clone()), + )) + } else { + // Use the task's own worktree + Some(( + task.worktree.as_ref().map(|w| w.path.clone()), + task.worktree.as_ref().map(|w| w.branch.clone()), + )) + } + } else { + None + } }; let (worktree_path, branch) = match task_info { diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index d5f2814..9e624c9 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -665,6 +665,9 @@ pub struct CreateTaskRequest { pub branched_from_task_id: Option<Uuid>, /// Conversation history to initialize the task with (JSON array of messages) pub conversation_history: Option<serde_json::Value>, + /// Task ID whose worktree this task shares. When set, this task reuses the supervisor's + /// worktree instead of creating its own, and should NOT have its worktree deleted during cleanup. + pub supervisor_worktree_task_id: Option<Uuid>, } /// Request payload for updating a task diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 7c9154f..b947cdd 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -691,9 +691,9 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task, contract_id, parent_task_id, depth, name, description, plan, priority, is_supervisor, is_red_team, repository_url, base_branch, target_branch, merge_mode, target_repo_path, completion_action, continue_from_task_id, copy_files, - branched_from_task_id, conversation_state + branched_from_task_id, conversation_state, supervisor_worktree_task_id ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) RETURNING * "#, ) @@ -716,6 +716,7 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task, .bind(©_files_json) .bind(&req.branched_from_task_id) .bind(&req.conversation_history) + .bind(&req.supervisor_worktree_task_id) .fetch_one(pool) .await } @@ -1105,9 +1106,9 @@ pub async fn create_task_for_owner( owner_id, contract_id, parent_task_id, depth, name, description, plan, priority, is_supervisor, is_red_team, repository_url, base_branch, target_branch, merge_mode, target_repo_path, completion_action, continue_from_task_id, copy_files, - branched_from_task_id, conversation_state + branched_from_task_id, conversation_state, supervisor_worktree_task_id ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING * "#, ) @@ -1131,6 +1132,7 @@ pub async fn create_task_for_owner( .bind(©_files_json) .bind(&req.branched_from_task_id) .bind(&req.conversation_history) + .bind(&req.supervisor_worktree_task_id) .fetch_one(pool) .await } @@ -2745,6 +2747,9 @@ pub struct TaskWorktreeInfo { pub id: Uuid, pub daemon_id: Option<Uuid>, pub overlay_path: Option<String>, + /// If set, this task shares the worktree of the specified supervisor task. + /// Should NOT have its worktree deleted during cleanup. + pub supervisor_worktree_task_id: Option<Uuid>, } /// List tasks in a contract with their daemon/worktree info. @@ -2755,7 +2760,7 @@ pub async fn list_contract_tasks_with_worktree_info( ) -> Result<Vec<TaskWorktreeInfo>, sqlx::Error> { sqlx::query_as::<_, TaskWorktreeInfo>( r#" - SELECT id, daemon_id, overlay_path + SELECT id, daemon_id, overlay_path, supervisor_worktree_task_id FROM tasks WHERE contract_id = $1 AND (daemon_id IS NOT NULL OR overlay_path IS NOT NULL) "#, diff --git a/makima/src/server/handlers/contract_chat.rs b/makima/src/server/handlers/contract_chat.rs index 98787be..c1ca3ed 100644 --- a/makima/src/server/handlers/contract_chat.rs +++ b/makima/src/server/handlers/contract_chat.rs @@ -1366,6 +1366,7 @@ async fn handle_contract_request( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; match repository::create_task_for_owner(pool, owner_id, create_req).await { @@ -1463,6 +1464,7 @@ async fn handle_contract_request( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; match repository::create_task_for_owner(pool, owner_id, create_req).await { @@ -2197,6 +2199,7 @@ async fn handle_contract_request( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; match repository::create_task_for_owner(pool, owner_id, create_req).await { @@ -2717,6 +2720,7 @@ async fn handle_contract_request( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; if repository::create_task_for_owner(pool, owner_id, task_req).await.is_ok() { diff --git a/makima/src/server/handlers/contracts.rs b/makima/src/server/handlers/contracts.rs index 3ad38da..7ed84e3 100644 --- a/makima/src/server/handlers/contracts.rs +++ b/makima/src/server/handlers/contracts.rs @@ -301,6 +301,7 @@ pub async fn create_contract( merge_mode: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Supervisor uses its own worktree }; match repository::create_task_for_owner(pool, auth.owner_id, supervisor_req).await { @@ -1673,7 +1674,19 @@ async fn cleanup_contract_worktrees( ); // Send cleanup command to each task's daemon + // Skip tasks that share a supervisor's worktree (they don't own the worktree) for task in tasks { + // Skip tasks that reuse the supervisor's worktree - the supervisor owns it + if task.supervisor_worktree_task_id.is_some() { + tracing::debug!( + task_id = %task.id, + supervisor_worktree_task_id = ?task.supervisor_worktree_task_id, + contract_id = %contract_id, + "Task shares supervisor worktree, skipping worktree cleanup" + ); + continue; + } + if let Some(daemon_id) = task.daemon_id { let cmd = crate::server::state::DaemonCommand::CleanupWorktree { task_id: task.id, diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs index 0dcab91..9ef6248 100644 --- a/makima/src/server/handlers/mesh.rs +++ b/makima/src/server/handlers/mesh.rs @@ -2623,6 +2623,7 @@ pub async fn reassign_task( checkpoint_sha: task.last_checkpoint_sha.clone(), branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; let new_task = match repository::create_task_for_owner(pool, auth.owner_id, create_req).await { @@ -3397,6 +3398,7 @@ pub async fn fork_task( checkpoint_sha: Some(checkpoint.commit_sha.clone()), branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; let new_task = match repository::create_task_for_owner(pool, auth.owner_id, create_req).await { @@ -3555,6 +3557,7 @@ pub async fn resume_from_checkpoint( checkpoint_sha: Some(checkpoint.commit_sha.clone()), branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; let new_task = match repository::create_task_for_owner(pool, auth.owner_id, create_req).await { @@ -3891,6 +3894,7 @@ pub async fn branch_task( checkpoint_sha: None, branched_from_task_id: Some(source_task_id), conversation_history, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; let task = match repository::create_task_for_owner(pool, auth.owner_id, create_req).await { diff --git a/makima/src/server/handlers/mesh_chat.rs b/makima/src/server/handlers/mesh_chat.rs index a74eb4f..623e66d 100644 --- a/makima/src/server/handlers/mesh_chat.rs +++ b/makima/src/server/handlers/mesh_chat.rs @@ -1021,6 +1021,7 @@ async fn handle_mesh_request( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; match repository::create_task_for_owner(pool, owner_id, create_req).await { diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs index a0a3a96..6f17103 100644 --- a/makima/src/server/handlers/mesh_supervisor.rs +++ b/makima/src/server/handlers/mesh_supervisor.rs @@ -608,6 +608,9 @@ pub async fn spawn_task( } // Create task request + // Share supervisor's worktree by default; separate worktree only when explicitly requested + let supervisor_worktree_task_id = if request.use_own_worktree { None } else { Some(supervisor_id) }; + let create_req = CreateTaskRequest { name: request.name.clone(), description: None, @@ -628,6 +631,7 @@ pub async fn spawn_task( copy_files: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id, }; // Create task in DB @@ -2650,6 +2654,7 @@ pub async fn spawn_red_team_task( checkpoint_sha: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Red team uses its own working area }; // Create task in DB diff --git a/makima/src/server/handlers/transcript_analysis.rs b/makima/src/server/handlers/transcript_analysis.rs index 3c283da..0a6ac7f 100644 --- a/makima/src/server/handlers/transcript_analysis.rs +++ b/makima/src/server/handlers/transcript_analysis.rs @@ -370,6 +370,7 @@ pub async fn create_contract_from_analysis( merge_mode: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; if let Ok(t) = repository::create_task_for_owner(pool, auth.owner_id, task_req).await { @@ -540,6 +541,7 @@ pub async fn update_contract_from_analysis( merge_mode: None, branched_from_task_id: None, conversation_history: None, + supervisor_worktree_task_id: None, // Not spawned by supervisor }; if let Ok(t) = repository::create_task_for_owner(pool, auth.owner_id, task_req).await { |
