Add git config shelley.no-trailer to suppress co-author injection

Ami Fischman created

When user runs 'git config set shelley.no-trailer true', the Co-authored-by
trailer is no longer added to git commit commands.

Adds test for isNoTrailerSet() method.

Change summary

claudetool/bash.go      | 15 +++++++++++++--
claudetool/bash_test.go | 39 +++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 2 deletions(-)

Detailed changes

claudetool/bash.go 🔗

@@ -96,6 +96,15 @@ func (b *BashTool) getWorkingDir() string {
 	return b.WorkingDir.Get()
 }
 
+// isNoTrailerSet checks if user has disabled co-author trailer via git config.
+func (b *BashTool) isNoTrailerSet() bool {
+	out, err := exec.Command("git", "config", "--get", "shelley.no-trailer").Output()
+	if err != nil {
+		return false
+	}
+	return strings.TrimSpace(string(out)) == "true"
+}
+
 const (
 	bashName        = "bash"
 	bashDescription = `Executes shell commands via bash -c, returning combined stdout/stderr.
@@ -207,8 +216,10 @@ func (b *BashTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut {
 		}
 	}
 
-	// Add co-author trailer to git commits
-	req.Command = bashkit.AddCoauthorTrailer(req.Command, "Co-authored-by: Shelley <shelley@exe.dev>")
+	// Add co-author trailer to git commits unless user has disabled it
+	if !b.isNoTrailerSet() {
+		req.Command = bashkit.AddCoauthorTrailer(req.Command, "Co-authored-by: Shelley <shelley@exe.dev>")
+	}
 
 	timeout := req.timeout(b.Timeouts)
 

claudetool/bash_test.go 🔗

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 	"syscall"
@@ -721,3 +722,41 @@ func waitForProcessDeath(t *testing.T, pid int) {
 		}
 	}
 }
+
+func TestIsNoTrailerSet(t *testing.T) {
+	bashTool := &BashTool{WorkingDir: NewMutableWorkingDir("/")}
+
+	// Test when config is not set (default)
+	t.Run("Default No Config", func(t *testing.T) {
+		if bashTool.isNoTrailerSet() {
+			t.Error("Expected isNoTrailerSet() to be false when not configured")
+		}
+	})
+
+	// Test when config is set to true
+	t.Run("Config Set True", func(t *testing.T) {
+		// Set the global config
+		cmd := exec.Command("git", "config", "--global", "shelley.no-trailer", "true")
+		if err := cmd.Run(); err != nil {
+			t.Skipf("Could not set git config: %v", err)
+		}
+		defer exec.Command("git", "config", "--global", "--unset", "shelley.no-trailer").Run()
+
+		if !bashTool.isNoTrailerSet() {
+			t.Error("Expected isNoTrailerSet() to be true when shelley.no-trailer=true")
+		}
+	})
+
+	// Test when config is set to false
+	t.Run("Config Set False", func(t *testing.T) {
+		cmd := exec.Command("git", "config", "--global", "shelley.no-trailer", "false")
+		if err := cmd.Run(); err != nil {
+			t.Skipf("Could not set git config: %v", err)
+		}
+		defer exec.Command("git", "config", "--global", "--unset", "shelley.no-trailer").Run()
+
+		if bashTool.isNoTrailerSet() {
+			t.Error("Expected isNoTrailerSet() to be false when shelley.no-trailer=false")
+		}
+	})
+}