From c7cb28a1da4dd02f5964f0dd1511f7318085d710 Mon Sep 17 00:00:00 2001 From: cexll Date: Tue, 13 Jan 2026 17:37:44 +0800 Subject: [PATCH] feat(codeagent-wrapper): default to skip-permissions and bypass-sandbox - Claude: enable --dangerously-skip-permissions by default (set CODEAGENT_SKIP_PERMISSIONS=false to disable) - Codex: enable --dangerously-bypass-approvals-and-sandbox by default (set CODEX_BYPASS_SANDBOX=false to disable) - Gemini: use positional argument instead of deprecated -p flag (except for stdin mode) - Add envFlagDefaultTrue helper for default-true env flags Generated with SWE-Agent.ai Co-Authored-By: SWE-Agent.ai --- codeagent-wrapper/backend.go | 11 +++++++++-- codeagent-wrapper/backend_test.go | 28 +++++++++++++++++++++------- codeagent-wrapper/config.go | 9 +++++++++ codeagent-wrapper/executor.go | 5 +++-- codeagent-wrapper/main_test.go | 9 ++++++--- docs/CODEAGENT-WRAPPER.md | 2 ++ 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/codeagent-wrapper/backend.go b/codeagent-wrapper/backend.go index 087c0ff..b13f8f0 100644 --- a/codeagent-wrapper/backend.go +++ b/codeagent-wrapper/backend.go @@ -111,7 +111,8 @@ func buildClaudeArgs(cfg *Config, targetArg string) []string { return nil } args := []string{"-p"} - if cfg.SkipPermissions || cfg.Yolo { + // Default to skip permissions unless CODEAGENT_SKIP_PERMISSIONS=false + if cfg.SkipPermissions || cfg.Yolo || envFlagDefaultTrue("CODEAGENT_SKIP_PERMISSIONS") { args = append(args, "--dangerously-skip-permissions") } @@ -179,7 +180,13 @@ func buildGeminiArgs(cfg *Config, targetArg string) []string { } // Note: gemini CLI doesn't support -C flag; workdir set via cmd.Dir - args = append(args, "-p", targetArg) + // Use positional argument instead of deprecated -p flag + // For stdin mode ("-"), use -p to read from stdin + if targetArg == "-" { + args = append(args, "-p", targetArg) + } else { + args = append(args, targetArg) + } return args } diff --git a/codeagent-wrapper/backend_test.go b/codeagent-wrapper/backend_test.go index 1e66b55..d0c2cec 100644 --- a/codeagent-wrapper/backend_test.go +++ b/codeagent-wrapper/backend_test.go @@ -11,7 +11,8 @@ import ( func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) { backend := ClaudeBackend{} - t.Run("new mode omits skip-permissions by default", func(t *testing.T) { + t.Run("new mode omits skip-permissions when env disabled", func(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") cfg := &Config{Mode: "new", WorkDir: "/repo"} got := backend.BuildArgs(cfg, "todo") want := []string{"-p", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "todo"} @@ -20,8 +21,8 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) { } }) - t.Run("new mode can opt-in skip-permissions", func(t *testing.T) { - cfg := &Config{Mode: "new", SkipPermissions: true} + t.Run("new mode includes skip-permissions by default", func(t *testing.T) { + cfg := &Config{Mode: "new", SkipPermissions: false} got := backend.BuildArgs(cfg, "-") want := []string{"-p", "--dangerously-skip-permissions", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "-"} if !reflect.DeepEqual(got, want) { @@ -30,6 +31,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) { }) t.Run("resume mode includes session id", func(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") cfg := &Config{Mode: "resume", SessionID: "sid-123", WorkDir: "/ignored"} got := backend.BuildArgs(cfg, "resume-task") want := []string{"-p", "--setting-sources", "", "-r", "sid-123", "--output-format", "stream-json", "--verbose", "resume-task"} @@ -39,6 +41,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) { }) t.Run("resume mode without session still returns base flags", func(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") cfg := &Config{Mode: "resume", WorkDir: "/ignored"} got := backend.BuildArgs(cfg, "follow-up") want := []string{"-p", "--setting-sources", "", "--output-format", "stream-json", "--verbose", "follow-up"} @@ -65,6 +68,7 @@ func TestClaudeBuildArgs_ModesAndPermissions(t *testing.T) { func TestBackendBuildArgs_Model(t *testing.T) { t.Run("claude includes --model when set", func(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") backend := ClaudeBackend{} cfg := &Config{Mode: "new", Model: "opus"} got := backend.BuildArgs(cfg, "todo") @@ -78,7 +82,7 @@ func TestBackendBuildArgs_Model(t *testing.T) { backend := GeminiBackend{} cfg := &Config{Mode: "new", Model: "gemini-3-pro-preview"} got := backend.BuildArgs(cfg, "task") - want := []string{"-o", "stream-json", "-y", "-m", "gemini-3-pro-preview", "-p", "task"} + want := []string{"-o", "stream-json", "-y", "-m", "gemini-3-pro-preview", "task"} if !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } @@ -103,7 +107,7 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) { backend := GeminiBackend{} cfg := &Config{Mode: "new", WorkDir: "/workspace"} got := backend.BuildArgs(cfg, "task") - want := []string{"-o", "stream-json", "-y", "-p", "task"} + want := []string{"-o", "stream-json", "-y", "task"} if !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } @@ -113,7 +117,7 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) { backend := GeminiBackend{} cfg := &Config{Mode: "resume", SessionID: "sid-999"} got := backend.BuildArgs(cfg, "resume") - want := []string{"-o", "stream-json", "-y", "-r", "sid-999", "-p", "resume"} + want := []string{"-o", "stream-json", "-y", "-r", "sid-999", "resume"} if !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } @@ -123,7 +127,7 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) { backend := GeminiBackend{} cfg := &Config{Mode: "resume"} got := backend.BuildArgs(cfg, "resume") - want := []string{"-o", "stream-json", "-y", "-p", "resume"} + want := []string{"-o", "stream-json", "-y", "resume"} if !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } @@ -136,6 +140,16 @@ func TestClaudeBuildArgs_GeminiAndCodexModes(t *testing.T) { } }) + t.Run("gemini stdin mode uses -p flag", func(t *testing.T) { + backend := GeminiBackend{} + cfg := &Config{Mode: "new"} + got := backend.BuildArgs(cfg, "-") + want := []string{"-o", "stream-json", "-y", "-p", "-"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %v, want %v", got, want) + } + }) + t.Run("codex build args omits bypass flag by default", func(t *testing.T) { const key = "CODEX_BYPASS_SANDBOX" t.Setenv(key, "false") diff --git a/codeagent-wrapper/config.go b/codeagent-wrapper/config.go index 0943232..c34d29a 100644 --- a/codeagent-wrapper/config.go +++ b/codeagent-wrapper/config.go @@ -112,6 +112,15 @@ func parseBoolFlag(val string, defaultValue bool) bool { } } +// envFlagDefaultTrue returns true unless the env var is explicitly set to false/0/no/off. +func envFlagDefaultTrue(key string) bool { + val, ok := os.LookupEnv(key) + if !ok { + return true + } + return parseBoolFlag(val, true) +} + func validateAgentName(name string) error { if strings.TrimSpace(name) == "" { return fmt.Errorf("agent name is empty") diff --git a/codeagent-wrapper/executor.go b/codeagent-wrapper/executor.go index 905243f..7cf751b 100644 --- a/codeagent-wrapper/executor.go +++ b/codeagent-wrapper/executor.go @@ -754,8 +754,9 @@ func buildCodexArgs(cfg *Config, targetArg string) []string { args := []string{"e"} - if cfg.Yolo || envFlagEnabled("CODEX_BYPASS_SANDBOX") { - logWarn("YOLO mode or CODEX_BYPASS_SANDBOX=true: running without approval/sandbox protection") + // Default to bypass sandbox unless CODEX_BYPASS_SANDBOX=false + if cfg.Yolo || envFlagDefaultTrue("CODEX_BYPASS_SANDBOX") { + logWarn("YOLO mode or CODEX_BYPASS_SANDBOX enabled: running without approval/sandbox protection") args = append(args, "--dangerously-bypass-approvals-and-sandbox") } diff --git a/codeagent-wrapper/main_test.go b/codeagent-wrapper/main_test.go index 37b1db5..908cdf2 100644 --- a/codeagent-wrapper/main_test.go +++ b/codeagent-wrapper/main_test.go @@ -1925,7 +1925,7 @@ func TestRunBuildCodexArgs_BypassSandboxEnvTrue(t *testing.T) { if err != nil { t.Fatalf("failed to read log file: %v", err) } - if !strings.Contains(string(data), "CODEX_BYPASS_SANDBOX=true") { + if !strings.Contains(string(data), "CODEX_BYPASS_SANDBOX enabled") { t.Fatalf("expected bypass warning log, got: %s", string(data)) } } @@ -1982,6 +1982,7 @@ func TestBackendSelectBackend_DefaultOnEmpty(t *testing.T) { } func TestBackendBuildArgs_CodexBackend(t *testing.T) { + t.Setenv("CODEX_BYPASS_SANDBOX", "false") backend := CodexBackend{} cfg := &Config{Mode: "new", WorkDir: "/test/dir"} got := backend.BuildArgs(cfg, "task") @@ -2003,6 +2004,7 @@ func TestBackendBuildArgs_CodexBackend(t *testing.T) { } func TestBackendBuildArgs_ClaudeBackend(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") backend := ClaudeBackend{} cfg := &Config{Mode: "new", WorkDir: defaultWorkdir} got := backend.BuildArgs(cfg, "todo") @@ -2022,6 +2024,7 @@ func TestBackendBuildArgs_ClaudeBackend(t *testing.T) { } func TestClaudeBackendBuildArgs_OutputValidation(t *testing.T) { + t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false") backend := ClaudeBackend{} cfg := &Config{Mode: "resume"} target := "ensure-flags" @@ -2042,7 +2045,7 @@ func TestBackendBuildArgs_GeminiBackend(t *testing.T) { backend := GeminiBackend{} cfg := &Config{Mode: "new"} got := backend.BuildArgs(cfg, "task") - want := []string{"-o", "stream-json", "-y", "-p", "task"} + want := []string{"-o", "stream-json", "-y", "task"} if len(got) != len(want) { t.Fatalf("length mismatch") } @@ -2063,7 +2066,7 @@ func TestGeminiBackendBuildArgs_OutputValidation(t *testing.T) { target := "prompt-data" args := backend.BuildArgs(cfg, target) - expected := []string{"-o", "stream-json", "-y", "-p"} + expected := []string{"-o", "stream-json", "-y"} if len(args) != len(expected)+1 { t.Fatalf("args length=%d, want %d", len(args), len(expected)+1) diff --git a/docs/CODEAGENT-WRAPPER.md b/docs/CODEAGENT-WRAPPER.md index f8a589d..d303805 100644 --- a/docs/CODEAGENT-WRAPPER.md +++ b/docs/CODEAGENT-WRAPPER.md @@ -322,6 +322,8 @@ Error: dependency backend_1701234567 failed | Variable | Default | Description | |----------|---------|-------------| | `CODEX_TIMEOUT` | 7200000 | Timeout in milliseconds | +| `CODEX_BYPASS_SANDBOX` | true | Bypass Codex sandbox/approval. Set `false` to disable | +| `CODEAGENT_SKIP_PERMISSIONS` | true | Skip Claude permission prompts. Set `false` to disable | ## Troubleshooting