summaryrefslogtreecommitdiff
path: root/internal/executor/container.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/container.go')
-rw-r--r--internal/executor/container.go56
1 files changed, 56 insertions, 0 deletions
diff --git a/internal/executor/container.go b/internal/executor/container.go
index 2c5b7d3..bfce750 100644
--- a/internal/executor/container.go
+++ b/internal/executor/container.go
@@ -271,6 +271,11 @@ func (r *ContainerRunner) Run(ctx context.Context, t *task.Task, e *storage.Exec
return fmt.Errorf("git push failed: %w\n%s", err, string(out))
}
} else {
+ // No commits pushed — check whether the agent left uncommitted work behind.
+ // If so, fail loudly: the work would be silently lost when the sandbox is deleted.
+ if err := detectUncommittedChanges(workspace); err != nil {
+ return err
+ }
r.Logger.Info("no new commits to push", "taskID", t.ID)
}
success = true
@@ -349,3 +354,54 @@ func (r *ContainerRunner) buildInnerCmd(t *task.Task, e *storage.Execution, isRe
return []string{"sh", "-c", claudeCmd.String()}
}
+// scaffoldPrefixes are files/dirs written by the harness into the workspace before the agent
+// runs. They are not part of the repo and must not trigger the uncommitted-changes check.
+var scaffoldPrefixes = []string{
+ ".claudomator-env",
+ ".claudomator-instructions.txt",
+ ".agent-home",
+}
+
+func isScaffold(path string) bool {
+ for _, p := range scaffoldPrefixes {
+ if path == p || strings.HasPrefix(path, p+"/") {
+ return true
+ }
+ }
+ return false
+}
+
+// detectUncommittedChanges returns an error if the workspace contains modified or
+// untracked source files that the agent forgot to commit. Scaffold files written by
+// the harness (.claudomator-env, .claudomator-instructions.txt, .agent-home/) are
+// excluded from the check.
+func detectUncommittedChanges(workspace string) error {
+ // Modified or staged tracked files
+ diffOut, err := exec.Command("git", "-c", "safe.directory=*", "-C", workspace,
+ "diff", "--name-only", "HEAD").CombinedOutput()
+ if err == nil {
+ for _, line := range strings.Split(strings.TrimSpace(string(diffOut)), "\n") {
+ if line != "" && !isScaffold(line) {
+ return fmt.Errorf("agent left uncommitted changes (work would be lost on sandbox deletion):\n%s\nInstructions must include: git add -A && git commit && git push origin master", strings.TrimSpace(string(diffOut)))
+ }
+ }
+ }
+
+ // Untracked new source files (excludes gitignored files)
+ lsOut, err := exec.Command("git", "-c", "safe.directory=*", "-C", workspace,
+ "ls-files", "--others", "--exclude-standard").CombinedOutput()
+ if err == nil {
+ var dirty []string
+ for _, line := range strings.Split(strings.TrimSpace(string(lsOut)), "\n") {
+ if line != "" && !isScaffold(line) {
+ dirty = append(dirty, line)
+ }
+ }
+ if len(dirty) > 0 {
+ return fmt.Errorf("agent left untracked files not committed (work would be lost on sandbox deletion):\n%s\nInstructions must include: git add -A && git commit && git push origin master", strings.Join(dirty, "\n"))
+ }
+ }
+
+ return nil
+}
+