summaryrefslogtreecommitdiff
path: root/internal/executor/container.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-26 08:56:04 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-26 08:56:04 +0000
commit909fa86bea1f55acc1ccb119e9509d2c724f6b5b (patch)
tree3fa90b9df01594ce08a833d00df8ca1d1bf0e8dd /internal/executor/container.go
parent6dcd26901b862aa680a1cb501f24183bcf854abc (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.go62
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 {