@@ -107,7 +107,7 @@ func (b *BashTool) isNoTrailerSet() bool {
const (
bashName = "bash"
- bashDescription = `Executes shell commands via bash -c, returning combined stdout/stderr.
+ bashDescription = `Executes shell commands via bash --login -c, returning combined stdout/stderr.
Bash state changes (working dir, variables, aliases) don't persist between calls.
With background=true, returns immediately, with output redirected to a file.
@@ -250,7 +250,7 @@ const (
)
func (b *BashTool) makeBashCommand(ctx context.Context, command string, out io.Writer) *exec.Cmd {
- cmd := exec.CommandContext(ctx, "bash", "-c", command)
+ cmd := exec.CommandContext(ctx, "bash", "--login", "-c", command)
// Use shared WorkingDir if available, then context, then Pwd fallback
cmd.Dir = b.getWorkingDir()
cmd.Stdin = nil
@@ -267,11 +267,9 @@ func (b *BashTool) makeBashCommand(ctx context.Context, command string, out io.W
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) // kill entire process group
}
cmd.WaitDelay = 15 * time.Second // prevent indefinite hangs when child processes keep pipes open
- // Remove SKETCH_MODEL_URL, SKETCH_PUB_KEY, SKETCH_MODEL_API_KEY,
- // and any other future SKETCH_ goodies from the environment.
- // ...except for SKETCH_PROXY_ID, which is intentionally available.
+ // Remove SHELLEY_CONVERSATION_ID so we control it explicitly below.
env := slices.DeleteFunc(os.Environ(), func(s string) bool {
- return strings.HasPrefix(s, "SKETCH_") && s != "SKETCH_PROXY_ID"
+ return strings.HasPrefix(s, "SHELLEY_CONVERSATION_ID=")
})
env = append(env, "SKETCH=1") // signal that this has been run by Sketch, sometimes useful for scripts
env = append(env, "EDITOR=/bin/false") // interactive editors won't work
@@ -207,23 +207,6 @@ func TestExecuteBash(t *testing.T) {
}
})
- // Test SKETCH=1 environment variable is set
- t.Run("SKETCH Environment Variable", func(t *testing.T) {
- req := bashInput{
- Command: "echo $SKETCH",
- }
-
- output, err := bashTool.executeBash(ctx, req, 5*time.Second)
- if err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
-
- want := "1\n"
- if output != want {
- t.Errorf("Expected SKETCH=1, got %q", output)
- }
- })
-
// Test SHELLEY_CONVERSATION_ID environment variable is set when configured
t.Run("SHELLEY_CONVERSATION_ID Environment Variable", func(t *testing.T) {
bashWithConvID := &BashTool{
@@ -263,6 +246,23 @@ func TestExecuteBash(t *testing.T) {
}
})
+ // Test that bash runs as a login shell (sources user profile)
+ t.Run("Login Shell", func(t *testing.T) {
+ req := bashInput{
+ Command: "shopt login_shell | grep -q on && echo login",
+ }
+
+ output, err := bashTool.executeBash(ctx, req, 5*time.Second)
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ want := "login\n"
+ if output != want {
+ t.Errorf("Expected bash to run as login shell, got %q", output)
+ }
+ })
+
// Test command with output to stderr
t.Run("Command with stderr", func(t *testing.T) {
req := bashInput{
@@ -327,7 +327,7 @@ func TestBackgroundBash(t *testing.T) {
Command string `json:"command"`
Background bool `json:"background"`
}{
- Command: "echo 'Hello from background' $SKETCH",
+ Command: "echo 'Hello from background'",
Background: true,
}
inputJSON, err := json.Marshal(inputObj)
@@ -387,8 +387,8 @@ func TestBackgroundBash(t *testing.T) {
}
// The implementation appends a completion message to the output
outputStr := string(outputContent)
- if !strings.Contains(outputStr, "Hello from background 1") {
- t.Errorf("Expected output to contain 'Hello from background 1', got %q", outputStr)
+ if !strings.Contains(outputStr, "Hello from background") {
+ t.Errorf("Expected output to contain 'Hello from background', got %q", outputStr)
}
if !strings.Contains(outputStr, "[background process completed]") {
t.Errorf("Expected output to contain completion message, got %q", outputStr)