diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-26 08:56:04 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-26 08:56:04 +0000 |
| commit | 909fa86bea1f55acc1ccb119e9509d2c724f6b5b (patch) | |
| tree | 3fa90b9df01594ce08a833d00df8ca1d1bf0e8dd /internal/executor/container.go | |
| parent | 6dcd26901b862aa680a1cb501f24183bcf854abc (diff) | |
fix: ensure story branch exists before cloning at task start
Add ensureStoryBranch() that runs git ls-remote to check, then clones
into a temp dir to create and push the branch if missing. Called before
the task's own clone so checkout is guaranteed to succeed.
Removes the post-checkout fallback hack added in the previous commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/executor/container.go')
| -rw-r--r-- | internal/executor/container.go | 62 |
1 files changed, 53 insertions, 9 deletions
diff --git a/internal/executor/container.go b/internal/executor/container.go index d90a273..61ac29c 100644 --- a/internal/executor/container.go +++ b/internal/executor/container.go @@ -58,6 +58,48 @@ func (r *ContainerRunner) ExecLogDir(execID string) string { return filepath.Join(r.LogDir, execID) } +// ensureStoryBranch checks whether branchName exists in remoteURL and creates +// it from main if not. Uses localPath as a reference clone for speed if set. +func (r *ContainerRunner) ensureStoryBranch(ctx context.Context, remoteURL, branchName, localPath string) error { + // Check if branch already exists. + out, err := r.command(ctx, "git", "ls-remote", "--heads", remoteURL, branchName).CombinedOutput() + if err == nil && len(strings.TrimSpace(string(out))) > 0 { + return nil // already exists + } + + r.Logger.Info("story branch missing, creating from main", "branch", branchName, "remote", remoteURL) + + // Clone into a temp dir so we can create the branch. + tmp, err := os.MkdirTemp("", "claudomator-branchsetup-*") + if err != nil { + return fmt.Errorf("mktemp for branch setup: %w", err) + } + defer os.RemoveAll(tmp) + + // Remove the dir git clone expects to create. + if err := os.Remove(tmp); err != nil { + return fmt.Errorf("removing tmp dir before clone: %w", err) + } + + var cloneArgs []string + if localPath != "" { + cloneArgs = []string{"clone", "--reference", localPath, remoteURL, tmp} + } else { + cloneArgs = []string{"clone", remoteURL, tmp} + } + if out, err := r.command(ctx, "git", cloneArgs...).CombinedOutput(); err != nil { + return fmt.Errorf("git clone for branch setup: %w\n%s", err, string(out)) + } + if out, err := r.command(ctx, "git", "-C", tmp, "checkout", "-b", branchName).CombinedOutput(); err != nil { + return fmt.Errorf("git checkout -b %q: %w\n%s", branchName, err, string(out)) + } + if out, err := r.command(ctx, "git", "-C", tmp, "push", "origin", branchName).CombinedOutput(); err != nil { + return fmt.Errorf("git push %q: %w\n%s", branchName, err, string(out)) + } + r.Logger.Info("story branch created and pushed", "branch", branchName) + return nil +} + func (r *ContainerRunner) Run(ctx context.Context, t *task.Task, e *storage.Execution) error { var err error repoURL := t.RepositoryURL @@ -114,7 +156,16 @@ func (r *ContainerRunner) Run(ctx context.Context, t *task.Task, e *storage.Exec storyBranch = t.BranchName } - // 2. Clone repo into workspace if not resuming. + // 2. Ensure story branch exists in the remote before cloning. + // If the branch is missing (e.g. story approved before fix, or branch push failed), + // create it from main using the project local path as a reference repo. + if storyBranch != "" && !isResume { + if err := r.ensureStoryBranch(ctx, repoURL, storyBranch, storyLocalPath); err != nil { + r.Logger.Warn("ensureStoryBranch failed (will attempt checkout anyway)", "branch", storyBranch, "error", err) + } + } + + // 3. Clone repo into workspace if not resuming. // git clone requires the target directory to not exist; remove the MkdirTemp-created dir first. if !isResume { if err := os.Remove(workspace); err != nil { @@ -133,14 +184,7 @@ func (r *ContainerRunner) Run(ctx context.Context, t *task.Task, e *storage.Exec if storyBranch != "" { r.Logger.Info("checking out story branch", "branch", storyBranch) if out, err := r.command(ctx, "git", "-C", workspace, "checkout", storyBranch).CombinedOutput(); err != nil { - // Branch doesn't exist in the remote yet — create it from HEAD and push. - r.Logger.Warn("story branch not found, creating from HEAD", "branch", storyBranch) - if out2, err2 := r.command(ctx, "git", "-C", workspace, "checkout", "-b", storyBranch).CombinedOutput(); err2 != nil { - return fmt.Errorf("git checkout story branch %q failed: %w\n%s\ncreate attempt: %s", storyBranch, err, string(out), string(out2)) - } - if out2, err2 := r.command(ctx, "git", "-C", workspace, "push", "origin", storyBranch).CombinedOutput(); err2 != nil { - r.Logger.Warn("push of auto-created story branch failed", "branch", storyBranch, "error", err2, "output", string(out2)) - } + return fmt.Errorf("git checkout story branch %q failed: %w\n%s", storyBranch, err, string(out)) } } if err = os.Chmod(workspace, 0755); err != nil { |
