summaryrefslogtreecommitdiff
path: root/internal/executor/container_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-21 21:34:45 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-21 21:34:45 +0000
commitbb599880f9b84088c2e9ffc63b1c2e0a7e9484ff (patch)
tree56be4c97ce6969ec3d5eb8fc3d04909b6e26be76 /internal/executor/container_test.go
parent16ea4a2e93a352064748fa2ceb983f26b2626583 (diff)
feat: fail loudly when agent leaves uncommitted work in sandbox
After a successful run with no commits pushed, detectUncommittedChanges checks for modified tracked files and untracked source files. If any exist the task fails with an explicit error rather than silently succeeding while the work evaporates when the sandbox is deleted. Scaffold files written by the harness (.claudomator-env, .claudomator-instructions.txt, .agent-home/) are excluded from the check. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/executor/container_test.go')
-rw-r--r--internal/executor/container_test.go101
1 files changed, 101 insertions, 0 deletions
diff --git a/internal/executor/container_test.go b/internal/executor/container_test.go
index f97f2b5..be80b51 100644
--- a/internal/executor/container_test.go
+++ b/internal/executor/container_test.go
@@ -230,6 +230,107 @@ func TestTailFile_ReturnsLastNLines(t *testing.T) {
}
}
+func TestDetectUncommittedChanges_ModifiedFile(t *testing.T) {
+ dir := t.TempDir()
+ run := func(args ...string) {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %s", args, out)
+ }
+ }
+ run("git", "init", dir)
+ run("git", "config", "user.email", "test@test.com")
+ run("git", "config", "user.name", "Test")
+ // Create and commit a file
+ if err := os.WriteFile(dir+"/main.go", []byte("package main"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ run("git", "add", "main.go")
+ run("git", "commit", "-m", "init")
+ // Now modify without committing — simulates agent that forgot to commit
+ if err := os.WriteFile(dir+"/main.go", []byte("package main\n// changed"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ err := detectUncommittedChanges(dir)
+ if err == nil {
+ t.Fatal("expected error for modified uncommitted file, got nil")
+ }
+ if !strings.Contains(err.Error(), "uncommitted") {
+ t.Errorf("error should mention uncommitted, got: %v", err)
+ }
+}
+
+func TestDetectUncommittedChanges_NewUntrackedSourceFile(t *testing.T) {
+ dir := t.TempDir()
+ run := func(args ...string) {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %s", args, out)
+ }
+ }
+ run("git", "init", dir)
+ run("git", "config", "user.email", "test@test.com")
+ run("git", "config", "user.name", "Test")
+ run("git", "commit", "--allow-empty", "-m", "init")
+ // Agent wrote a new file but never committed it
+ if err := os.WriteFile(dir+"/newfile.go", []byte("package main"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ err := detectUncommittedChanges(dir)
+ if err == nil {
+ t.Fatal("expected error for new untracked source file, got nil")
+ }
+}
+
+func TestDetectUncommittedChanges_ScaffoldFilesIgnored(t *testing.T) {
+ dir := t.TempDir()
+ run := func(args ...string) {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %s", args, out)
+ }
+ }
+ run("git", "init", dir)
+ run("git", "config", "user.email", "test@test.com")
+ run("git", "config", "user.name", "Test")
+ run("git", "commit", "--allow-empty", "-m", "init")
+ // Write only scaffold files that the harness injects — should not trigger error
+ _ = os.WriteFile(dir+"/.claudomator-env", []byte("KEY=val"), 0600)
+ _ = os.WriteFile(dir+"/.claudomator-instructions.txt", []byte("do stuff"), 0644)
+ _ = os.MkdirAll(dir+"/.agent-home/.claude", 0755)
+ err := detectUncommittedChanges(dir)
+ if err != nil {
+ t.Errorf("scaffold files should not trigger uncommitted error, got: %v", err)
+ }
+}
+
+func TestDetectUncommittedChanges_CleanRepo(t *testing.T) {
+ dir := t.TempDir()
+ run := func(args ...string) {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v: %s", args, out)
+ }
+ }
+ run("git", "init", dir)
+ run("git", "config", "user.email", "test@test.com")
+ run("git", "config", "user.name", "Test")
+ if err := os.WriteFile(dir+"/main.go", []byte("package main"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ run("git", "add", "main.go")
+ run("git", "commit", "-m", "init")
+ // No modifications — should pass
+ err := detectUncommittedChanges(dir)
+ if err != nil {
+ t.Errorf("clean repo should not error, got: %v", err)
+ }
+}
+
func TestGitSafe_PrependsSafeDirectory(t *testing.T) {
got := gitSafe("-C", "/some/path", "status")
want := []string{"-c", "safe.directory=*", "-C", "/some/path", "status"}