summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-18 17:20:32 +0000
committersoryu <soryu@soryu.co>2026-02-18 17:20:32 +0000
commit28b191cc0b0e69b864191673df9c141730c93e4f (patch)
treee96fb3e21a37e5f3ef6c6937de13f87ad357d7d7
parent720ebdac2f64ce18e1de68d070cd3fe46f44547c (diff)
downloadsoryu-28b191cc0b0e69b864191673df9c141730c93e4f.tar.gz
soryu-28b191cc0b0e69b864191673df9c141730c93e4f.zip
fix: prevent directive step failure when PR branch is deleted after merge
Stop using pr_branch as base branch for step tasks since it may be auto-deleted by GitHub after PR merge. Instead always use continue_from_task_id or fall back to base_branch. Also add a safety net in create_worktree that detects when a base branch ref no longer exists and falls back to the repo's default branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--makima/src/daemon/worktree/manager.rs43
-rw-r--r--makima/src/orchestration/directive.rs45
2 files changed, 56 insertions, 32 deletions
diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs
index c27bcf6..30618ea 100644
--- a/makima/src/daemon/worktree/manager.rs
+++ b/makima/src/daemon/worktree/manager.rs
@@ -484,7 +484,9 @@ impl WorktreeManager {
"Creating worktree with new branch"
);
- // Prefer origin/{base_branch} to get latest remote state
+ // Prefer origin/{base_branch} to get latest remote state.
+ // If neither origin/{base_branch} nor {base_branch} exist (e.g. PR branch
+ // was deleted after merge), fall back to the repo's default branch.
let origin_ref = format!("origin/{}", base_branch);
let has_origin_ref = Command::new("git")
.args(["rev-parse", "--verify", &format!("refs/remotes/{}", origin_ref)])
@@ -494,10 +496,43 @@ impl WorktreeManager {
.map(|o| o.status.success())
.unwrap_or(false);
- let start_point = if has_origin_ref {
- origin_ref.as_str()
+ let has_local_ref = if !has_origin_ref {
+ Command::new("git")
+ .args(["rev-parse", "--verify", &format!("refs/heads/{}", base_branch)])
+ .current_dir(source_repo)
+ .output()
+ .await
+ .map(|o| o.status.success())
+ .unwrap_or(false)
} else {
- base_branch
+ false // don't need to check — we already have origin ref
+ };
+
+ let start_point: String = if has_origin_ref {
+ origin_ref
+ } else if has_local_ref {
+ base_branch.to_string()
+ } else {
+ // Branch doesn't exist (likely deleted after PR merge) — use default branch
+ tracing::warn!(
+ task_id = %task_id,
+ base_branch = %base_branch,
+ "Base branch ref not found, falling back to default branch"
+ );
+ let default_branch = self.detect_default_branch(source_repo).await?;
+ let default_origin = format!("origin/{}", default_branch);
+ let has_default_origin = Command::new("git")
+ .args(["rev-parse", "--verify", &format!("refs/remotes/{}", default_origin)])
+ .current_dir(source_repo)
+ .output()
+ .await
+ .map(|o| o.status.success())
+ .unwrap_or(false);
+ if has_default_origin {
+ default_origin
+ } else {
+ default_branch
+ }
};
tracing::info!(
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
index a6bb85b..af6b18c 100644
--- a/makima/src/orchestration/directive.rs
+++ b/makima/src/orchestration/directive.rs
@@ -103,36 +103,25 @@ impl DirectiveOrchestrator {
};
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:
- // 1) Use the directive's PR branch as base (contains all previous merged work)
- // 2) Fall back to the last completed step's task for worktree continuation
+ // If no dependency tasks resolved, try to continue from the last completed step's worktree.
+ // We never use pr_branch as base because it may have been deleted after PR merge.
let effective_base_branch = if continue_from_task_id.is_none() {
- if step.pr_branch.is_some() {
- tracing::info!(
- step_id = %step.step_id,
- pr_branch = ?step.pr_branch,
- "Step has no deps — using directive PR branch as base"
- );
- step.pr_branch.as_deref()
- } else {
- // No PR branch yet — try to continue from the last completed step's worktree
- match repository::get_last_completed_step_task_id(
- &self.pool,
- step.directive_id,
- )
- .await
- {
- Ok(Some(task_id)) => {
- tracing::info!(
- step_id = %step.step_id,
- continue_from = %task_id,
- "Step has no deps, no PR branch — continuing from last completed task"
- );
- continue_from_task_id = Some(task_id);
- step.base_branch.as_deref()
- }
- _ => step.base_branch.as_deref(),
+ match repository::get_last_completed_step_task_id(
+ &self.pool,
+ step.directive_id,
+ )
+ .await
+ {
+ Ok(Some(task_id)) => {
+ tracing::info!(
+ step_id = %step.step_id,
+ continue_from = %task_id,
+ "Step has no deps — continuing from last completed task"
+ );
+ continue_from_task_id = Some(task_id);
+ step.base_branch.as_deref()
}
+ _ => step.base_branch.as_deref(),
}
} else {
step.base_branch.as_deref()