diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/executor/claude.go | 34 |
1 files changed, 21 insertions, 13 deletions
diff --git a/internal/executor/claude.go b/internal/executor/claude.go index 2faeff3..ad1c4e3 100644 --- a/internal/executor/claude.go +++ b/internal/executor/claude.go @@ -190,10 +190,13 @@ func setupSandbox(projectDir string) (string, error) { return tempDir, nil } -// teardownSandbox verifies the sandbox is clean, pushes new commits to the -// canonical remote (the bare repo), then fast-forward pulls them into the -// working copy. This avoids writing git objects directly into the working copy -// which causes permission errors on shared/mixed-owner repos. +// teardownSandbox verifies the sandbox is clean and pushes new commits to the +// canonical bare repo. If the push is rejected because another task pushed +// concurrently, it fetches and rebases then retries once. +// +// The working copy (projectDir) is NOT updated automatically — it is the +// developer's workspace and is pulled manually. This avoids permission errors +// from mixed-owner .git/objects directories. func teardownSandbox(projectDir, sandboxDir string, logger *slog.Logger) error { // Fail if agent left uncommitted changes. out, err := exec.Command("git", "-C", sandboxDir, "status", "--porcelain").Output() @@ -204,7 +207,7 @@ func teardownSandbox(projectDir, sandboxDir string, logger *slog.Logger) error { return fmt.Errorf("uncommitted changes in sandbox (agent must commit all work):\n%s", out) } - // Check whether there are any new commits. + // Check whether there are any new commits to push. ahead, err := exec.Command("git", "-C", sandboxDir, "rev-list", "--count", "origin/HEAD..HEAD").Output() if err != nil { logger.Warn("could not determine commits ahead of origin; proceeding", "err", err) @@ -216,16 +219,21 @@ func teardownSandbox(projectDir, sandboxDir string, logger *slog.Logger) error { // Push from sandbox → bare repo (sandbox's origin is the bare repo). if out, err := exec.Command("git", "-C", sandboxDir, "push", "origin", "HEAD").CombinedOutput(); err != nil { - return fmt.Errorf("git push to origin: %w\n%s", err, out) - } - - // Pull bare repo → working copy. - src := sandboxCloneSource(projectDir) - if out, err := exec.Command("git", "-C", projectDir, "pull", "--ff-only", src).CombinedOutput(); err != nil { - return fmt.Errorf("git pull into project_dir: %w\n%s", err, out) + // If rejected due to concurrent push, fetch+rebase and retry once. + if strings.Contains(string(out), "fetch first") || strings.Contains(string(out), "non-fast-forward") { + logger.Info("push rejected (concurrent task); rebasing and retrying", "sandbox", sandboxDir) + if out2, err2 := exec.Command("git", "-C", sandboxDir, "pull", "--rebase", "origin", "master").CombinedOutput(); err2 != nil { + return fmt.Errorf("git rebase before retry push: %w\n%s", err2, out2) + } + if out3, err3 := exec.Command("git", "-C", sandboxDir, "push", "origin", "HEAD").CombinedOutput(); err3 != nil { + return fmt.Errorf("git push to origin (after rebase): %w\n%s", err3, out3) + } + } else { + return fmt.Errorf("git push to origin: %w\n%s", err, out) + } } - logger.Info("sandbox pushed and working copy updated", "sandbox", sandboxDir, "project_dir", projectDir) + logger.Info("sandbox pushed to bare repo", "sandbox", sandboxDir) os.RemoveAll(sandboxDir) return nil } |
