allow configuring shell (#157)

Ed Zynda created

Change summary

README.md                         | 22 ++++++++++++++++++++++
internal/config/config.go         | 15 +++++++++++++++
internal/llm/tools/shell/shell.go | 27 ++++++++++++++++++++++++---
3 files changed, 61 insertions(+), 3 deletions(-)

Detailed changes

README.md 🔗

@@ -96,6 +96,24 @@ You can configure OpenCode using environment variables:
 | `AZURE_OPENAI_ENDPOINT`    | For Azure OpenAI models                                |
 | `AZURE_OPENAI_API_KEY`     | For Azure OpenAI models (optional when using Entra ID) |
 | `AZURE_OPENAI_API_VERSION` | For Azure OpenAI models                                |
+| `SHELL`                    | Default shell to use (if not specified in config)      |
+
+### Shell Configuration
+
+OpenCode allows you to configure the shell used by the bash tool. By default, it uses the shell specified in the `SHELL` environment variable, or falls back to `/bin/bash` if not set.
+
+You can override this in your configuration file:
+
+```json
+{
+  "shell": {
+    "path": "/bin/zsh",
+    "args": ["-l"]
+  }
+}
+```
+
+This is useful if you want to use a different shell than your default system shell, or if you need to pass specific arguments to the shell.
 
 ### Configuration File Structure
 
@@ -136,6 +154,10 @@ You can configure OpenCode using environment variables:
       "maxTokens": 80
     }
   },
+  "shell": {
+    "path": "/bin/bash",
+    "args": ["-l"]
+  },
   "mcpServers": {
     "example": {
       "type": "stdio",

internal/config/config.go 🔗

@@ -73,6 +73,12 @@ type TUIConfig struct {
 	Theme string `json:"theme,omitempty"`
 }
 
+// ShellConfig defines the configuration for the shell used by the bash tool.
+type ShellConfig struct {
+	Path string   `json:"path,omitempty"`
+	Args []string `json:"args,omitempty"`
+}
+
 // Config is the main configuration structure for the application.
 type Config struct {
 	Data         Data                              `json:"data"`
@@ -85,6 +91,7 @@ type Config struct {
 	DebugLSP     bool                              `json:"debugLSP,omitempty"`
 	ContextPaths []string                          `json:"contextPaths,omitempty"`
 	TUI          TUIConfig                         `json:"tui"`
+	Shell        ShellConfig                       `json:"shell,omitempty"`
 	AutoCompact  bool                              `json:"autoCompact,omitempty"`
 }
 
@@ -217,6 +224,14 @@ func setDefaults(debug bool) {
 	viper.SetDefault("tui.theme", "opencode")
 	viper.SetDefault("autoCompact", true)
 
+	// Set default shell from environment or fallback to /bin/bash
+	shellPath := os.Getenv("SHELL")
+	if shellPath == "" {
+		shellPath = "/bin/bash"
+	}
+	viper.SetDefault("shell.path", shellPath)
+	viper.SetDefault("shell.args", []string{"-l"})
+
 	if debug {
 		viper.SetDefault("debug", true)
 		viper.Set("log.level", "debug")

internal/llm/tools/shell/shell.go 🔗

@@ -11,6 +11,8 @@ import (
 	"sync"
 	"syscall"
 	"time"
+
+	"github.com/opencode-ai/opencode/internal/config"
 )
 
 type PersistentShell struct {
@@ -57,12 +59,31 @@ func GetPersistentShell(workingDir string) *PersistentShell {
 }
 
 func newPersistentShell(cwd string) *PersistentShell {
-	shellPath := os.Getenv("SHELL")
+	// Get shell configuration from config
+	cfg := config.Get()
+	
+	// Default to environment variable if config is not set or nil
+	var shellPath string
+	var shellArgs []string
+	
+	if cfg != nil {
+		shellPath = cfg.Shell.Path
+		shellArgs = cfg.Shell.Args
+	}
+	
 	if shellPath == "" {
-		shellPath = "/bin/bash"
+		shellPath = os.Getenv("SHELL")
+		if shellPath == "" {
+			shellPath = "/bin/bash"
+		}
+	}
+	
+	// Default shell args
+	if len(shellArgs) == 0 {
+		shellArgs = []string{"-l"}
 	}
 
-	cmd := exec.Command(shellPath, "-l")
+	cmd := exec.Command(shellPath, shellArgs...)
 	cmd.Dir = cwd
 
 	stdinPipe, err := cmd.StdinPipe()