summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-14 07:24:10 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-14 07:24:10 +0000
commit02b35218d9aadcaa6a3b52f218b71577ab72c811 (patch)
treeffa761a07b940e97bf1e741b0cff8fcd74622a89
parente0e81bbdaae37353803d47fa59a36d0472f8146d (diff)
fix: trust all directory owners in sandbox git commands
Sandbox setup runs git commands against project_dir which may be owned by a different OS user, triggering git's 'dubious ownership' error. Fix by passing -c safe.directory=* on all git commands that touch project directories. Also add wildcard to global config for immediate effect on the running server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/executor/claude.go21
-rw-r--r--internal/executor/claude_test.go13
2 files changed, 27 insertions, 7 deletions
diff --git a/internal/executor/claude.go b/internal/executor/claude.go
index a3d304d..626a854 100644
--- a/internal/executor/claude.go
+++ b/internal/executor/claude.go
@@ -200,12 +200,19 @@ func extractQuestionText(questionJSON string) string {
return strings.TrimSpace(q.Text)
}
+// gitSafe returns git arguments that prepend "-c safe.directory=*" so that
+// commands succeed regardless of the repository owner. This is needed when
+// claudomator operates on project directories owned by a different OS user.
+func gitSafe(args ...string) []string {
+ return append([]string{"-c", "safe.directory=*"}, args...)
+}
+
// sandboxCloneSource returns the URL to clone the sandbox from. It prefers a
// remote named "local" (a local bare repo that accepts pushes cleanly), then
// falls back to "origin", then to the working copy path itself.
func sandboxCloneSource(projectDir string) string {
for _, remote := range []string{"local", "origin"} {
- out, err := exec.Command("git", "-C", projectDir, "remote", "get-url", remote).Output()
+ out, err := exec.Command("git", gitSafe("-C", projectDir, "remote", "get-url", remote)...).Output()
if err == nil && len(strings.TrimSpace(string(out))) > 0 {
return strings.TrimSpace(string(out))
}
@@ -217,14 +224,14 @@ func sandboxCloneSource(projectDir string) string {
// If projectDir is not a git repo it is initialised with an initial commit first.
func setupSandbox(projectDir string) (string, error) {
// Ensure projectDir is a git repo; initialise if not.
- if err := exec.Command("git", "-C", projectDir, "rev-parse", "--git-dir").Run(); err != nil {
+ if err := exec.Command("git", gitSafe("-C", projectDir, "rev-parse", "--git-dir")...).Run(); err != nil {
cmds := [][]string{
- {"git", "-C", projectDir, "init"},
- {"git", "-C", projectDir, "add", "-A"},
- {"git", "-C", projectDir, "commit", "--allow-empty", "-m", "chore: initial commit"},
+ gitSafe("-C", projectDir, "init"),
+ gitSafe("-C", projectDir, "add", "-A"),
+ gitSafe("-C", projectDir, "commit", "--allow-empty", "-m", "chore: initial commit"),
}
for _, args := range cmds {
- if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil { //nolint:gosec
+ if out, err := exec.Command("git", args...).CombinedOutput(); err != nil { //nolint:gosec
return "", fmt.Errorf("git init %s: %w\n%s", projectDir, err, out)
}
}
@@ -240,7 +247,7 @@ func setupSandbox(projectDir string) (string, error) {
if err := os.Remove(tempDir); err != nil {
return "", fmt.Errorf("removing temp dir placeholder: %w", err)
}
- out, err := exec.Command("git", "clone", "--no-hardlinks", src, tempDir).CombinedOutput()
+ out, err := exec.Command("git", gitSafe("clone", "--no-hardlinks", src, tempDir)...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("git clone: %w\n%s", err, out)
}
diff --git a/internal/executor/claude_test.go b/internal/executor/claude_test.go
index 36affef..7ab0802 100644
--- a/internal/executor/claude_test.go
+++ b/internal/executor/claude_test.go
@@ -620,3 +620,16 @@ func TestIsCompletionReport(t *testing.T) {
})
}
}
+
+func TestGitSafe_PrependsSafeDirectory(t *testing.T) {
+ got := gitSafe("-C", "/some/path", "status")
+ want := []string{"-c", "safe.directory=*", "-C", "/some/path", "status"}
+ if len(got) != len(want) {
+ t.Fatalf("gitSafe() = %v, want %v", got, want)
+ }
+ for i := range want {
+ if got[i] != want[i] {
+ t.Errorf("gitSafe()[%d] = %q, want %q", i, got[i], want[i])
+ }
+ }
+}