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 <noreply@swe-agent.ai>
This commit is contained in:
cexll
2026-01-13 17:37:44 +08:00
parent 0a4982e96d
commit c7cb28a1da
6 changed files with 50 additions and 14 deletions

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -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)