summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/executor/claude.go23
-rw-r--r--internal/executor/claude_test.go106
2 files changed, 129 insertions, 0 deletions
diff --git a/internal/executor/claude.go b/internal/executor/claude.go
index 4d92cd0..f8b0ac2 100644
--- a/internal/executor/claude.go
+++ b/internal/executor/claude.go
@@ -292,6 +292,29 @@ func teardownSandbox(projectDir, sandboxDir, startHEAD string, logger *slog.Logg
}
if len(strings.TrimSpace(string(out))) > 0 {
logger.Info("autocommitting uncommitted changes", "sandbox", sandboxDir)
+
+ // Run build before autocommitting.
+ if _, err := os.Stat(filepath.Join(sandboxDir, "Makefile")); err == nil {
+ logger.Info("running 'make build' before autocommit", "sandbox", sandboxDir)
+ if buildOut, buildErr := exec.Command("make", "-C", sandboxDir, "build").CombinedOutput(); buildErr != nil {
+ return fmt.Errorf("build failed before autocommit: %w\n%s", buildErr, buildOut)
+ }
+ } else if _, err := os.Stat(filepath.Join(sandboxDir, "gradlew")); err == nil {
+ logger.Info("running './gradlew build' before autocommit", "sandbox", sandboxDir)
+ cmd := exec.Command("./gradlew", "build")
+ cmd.Dir = sandboxDir
+ if buildOut, buildErr := cmd.CombinedOutput(); buildErr != nil {
+ return fmt.Errorf("build failed before autocommit: %w\n%s", buildErr, buildOut)
+ }
+ } else if _, err := os.Stat(filepath.Join(sandboxDir, "go.mod")); err == nil {
+ logger.Info("running 'go build ./...' before autocommit", "sandbox", sandboxDir)
+ cmd := exec.Command("go", "build", "./...")
+ cmd.Dir = sandboxDir
+ if buildOut, buildErr := cmd.CombinedOutput(); buildErr != nil {
+ return fmt.Errorf("build failed before autocommit: %w\n%s", buildErr, buildOut)
+ }
+ }
+
cmds := [][]string{
gitSafe("-C", sandboxDir, "add", "-A"),
gitSafe("-C", sandboxDir, "commit", "-m", "chore: autocommit uncommitted changes"),
diff --git a/internal/executor/claude_test.go b/internal/executor/claude_test.go
index 02d1b2e..04ea6b7 100644
--- a/internal/executor/claude_test.go
+++ b/internal/executor/claude_test.go
@@ -512,6 +512,112 @@ func TestTeardownSandbox_AutocommitsChanges(t *testing.T) {
}
}
+func TestTeardownSandbox_BuildFailure_BlocksAutocommit(t *testing.T) {
+ bare := t.TempDir()
+ if out, err := exec.Command("git", "init", "--bare", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git init bare: %v\n%s", err, out)
+ }
+
+ sandbox := t.TempDir()
+ initGitRepo(t, sandbox)
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "remote", "add", "origin", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git remote add: %v\n%s", err, out)
+ }
+
+ // Capture startHEAD
+ headOut, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "rev-parse", "HEAD").Output()
+ if err != nil {
+ t.Fatalf("rev-parse HEAD: %v", err)
+ }
+ startHEAD := strings.TrimSpace(string(headOut))
+
+ // Leave an uncommitted file.
+ if err := os.WriteFile(filepath.Join(sandbox, "dirty.txt"), []byte("dirty"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ // Add a failing Makefile.
+ makefile := "build:\n\t@echo 'build failed'\n\texit 1\n"
+ if err := os.WriteFile(filepath.Join(sandbox, "Makefile"), []byte(makefile), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ logger := slog.New(slog.NewTextHandler(io.Discard, nil))
+ execRecord := &storage.Execution{}
+
+ err = teardownSandbox("", sandbox, startHEAD, logger, execRecord)
+ if err == nil {
+ t.Error("expected teardown to fail due to build failure, but it succeeded")
+ } else if !strings.Contains(err.Error(), "build failed before autocommit") {
+ t.Errorf("expected build failure error message, got: %v", err)
+ }
+
+ // Sandbox should NOT be removed if teardown failed.
+ if _, statErr := os.Stat(sandbox); os.IsNotExist(statErr) {
+ t.Error("sandbox should have been preserved after build failure")
+ }
+
+ // Verify no new commit in bare repo.
+ out, err := exec.Command("git", "-C", bare, "log", "HEAD").CombinedOutput()
+ if strings.Contains(string(out), "chore: autocommit uncommitted changes") {
+ t.Error("autocommit should not have been pushed after build failure")
+ }
+}
+
+func TestTeardownSandbox_BuildSuccess_ProceedsToAutocommit(t *testing.T) {
+ bare := t.TempDir()
+ if out, err := exec.Command("git", "init", "--bare", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git init bare: %v\n%s", err, out)
+ }
+
+ sandbox := t.TempDir()
+ initGitRepo(t, sandbox)
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "remote", "add", "origin", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git remote add: %v\n%s", err, out)
+ }
+
+ // Capture startHEAD
+ headOut, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "rev-parse", "HEAD").Output()
+ if err != nil {
+ t.Fatalf("rev-parse HEAD: %v", err)
+ }
+ startHEAD := strings.TrimSpace(string(headOut))
+
+ // Leave an uncommitted file.
+ if err := os.WriteFile(filepath.Join(sandbox, "dirty.txt"), []byte("dirty"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ // Add a successful Makefile.
+ makefile := "build:\n\t@echo 'build succeeded'\n"
+ if err := os.WriteFile(filepath.Join(sandbox, "Makefile"), []byte(makefile), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ logger := slog.New(slog.NewTextHandler(io.Discard, nil))
+ execRecord := &storage.Execution{}
+
+ err = teardownSandbox("", sandbox, startHEAD, logger, execRecord)
+ if err != nil {
+ t.Fatalf("expected teardown to succeed after build success, got error: %v", err)
+ }
+
+ // Sandbox should be removed after success.
+ if _, statErr := os.Stat(sandbox); !os.IsNotExist(statErr) {
+ t.Error("sandbox should have been removed after successful build and autocommit")
+ }
+
+ // Verify new commit in bare repo.
+ out, err := exec.Command("git", "-C", bare, "log", "-1", "--pretty=%B").Output()
+ if err != nil {
+ t.Fatalf("git log in bare repo: %v", err)
+ }
+ if !strings.Contains(string(out), "chore: autocommit uncommitted changes") {
+ t.Errorf("expected autocommit message in log, got: %q", string(out))
+ }
+}
+
+
func TestTeardownSandbox_CleanSandboxWithNoNewCommits_RemovesSandbox(t *testing.T) {
src := t.TempDir()
initGitRepo(t, src)