fix(codeagent-wrapper): propagate SkipPermissions to parallel tasks (#113)

Parallel task execution was not inheriting the --skip-permissions flag,
causing permission prompts to appear for parallel tasks while single
tasks worked correctly.

Changes:
- Add SkipPermissions field to TaskSpec struct
- Parse skip_permissions/skip-permissions in parallel task config
- Inherit SkipPermissions from CLI args to parallel tasks
- Pass SkipPermissions when creating task Config in executor

Generated with SWE-Agent.ai

Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
This commit is contained in:
cexll
2026-01-14 11:50:36 +08:00
parent 8f05626075
commit 55a574280a
5 changed files with 83 additions and 1 deletions

View File

@@ -46,6 +46,7 @@ type TaskSpec struct {
ReasoningEffort string `json:"reasoning_effort,omitempty"`
Agent string `json:"agent,omitempty"`
PromptFile string `json:"prompt_file,omitempty"`
SkipPermissions bool `json:"skip_permissions,omitempty"`
Mode string `json:"-"`
UseStdin bool `json:"-"`
Context context.Context `json:"-"`
@@ -201,6 +202,12 @@ func parseParallelConfig(data []byte) (*ParallelConfig, error) {
case "agent":
agentSpecified = true
task.Agent = value
case "skip_permissions", "skip-permissions":
if value == "" {
task.SkipPermissions = true
continue
}
task.SkipPermissions = parseBoolFlag(value, false)
case "dependencies":
for _, dep := range strings.Split(value, ",") {
dep = strings.TrimSpace(dep)

View File

@@ -815,6 +815,7 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
WorkDir: taskSpec.WorkDir,
Model: taskSpec.Model,
ReasoningEffort: taskSpec.ReasoningEffort,
SkipPermissions: taskSpec.SkipPermissions,
Backend: defaultBackendName,
}

View File

@@ -625,6 +625,27 @@ func TestExecutorRunCodexTaskWithContext(t *testing.T) {
}
})
t.Run("claudeSkipPermissionsPropagatesFromTaskSpec", func(t *testing.T) {
t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false")
var gotArgs []string
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
gotArgs = append([]string(nil), args...)
return &execFakeRunner{
stdout: newReasonReadCloser(`{"type":"item.completed","item":{"type":"agent_message","text":"ok"}}`),
process: &execFakeProcess{pid: 15},
}
}
_ = closeLogger()
res := runCodexTaskWithContext(context.Background(), TaskSpec{ID: "task-skip", Task: "payload", WorkDir: ".", SkipPermissions: true}, ClaudeBackend{}, nil, false, false, 1)
if res.ExitCode != 0 || res.Error != "" {
t.Fatalf("unexpected result: %+v", res)
}
if !slices.Contains(gotArgs, "--dangerously-skip-permissions") {
t.Fatalf("expected --dangerously-skip-permissions in args, got %v", gotArgs)
}
})
t.Run("missingMessage", func(t *testing.T) {
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
return &execFakeRunner{

View File

@@ -177,6 +177,7 @@ func run() (exitCode int) {
backendName := defaultBackendName
model := ""
fullOutput := false
skipPermissions := envFlagEnabled("CODEAGENT_SKIP_PERMISSIONS")
var extras []string
for i := 0; i < len(args); i++ {
@@ -214,13 +215,19 @@ func run() (exitCode int) {
return 1
}
model = value
case arg == "--skip-permissions", arg == "--dangerously-skip-permissions":
skipPermissions = true
case strings.HasPrefix(arg, "--skip-permissions="):
skipPermissions = parseBoolFlag(strings.TrimPrefix(arg, "--skip-permissions="), skipPermissions)
case strings.HasPrefix(arg, "--dangerously-skip-permissions="):
skipPermissions = parseBoolFlag(strings.TrimPrefix(arg, "--dangerously-skip-permissions="), skipPermissions)
default:
extras = append(extras, arg)
}
}
if len(extras) > 0 {
fmt.Fprintln(os.Stderr, "ERROR: --parallel reads its task configuration from stdin; only --backend, --model and --full-output are allowed.")
fmt.Fprintln(os.Stderr, "ERROR: --parallel reads its task configuration from stdin; only --backend, --model, --full-output and --skip-permissions are allowed.")
fmt.Fprintln(os.Stderr, "Usage examples:")
fmt.Fprintf(os.Stderr, " %s --parallel < tasks.txt\n", name)
fmt.Fprintf(os.Stderr, " echo '...' | %s --parallel\n", name)
@@ -257,6 +264,7 @@ func run() (exitCode int) {
if strings.TrimSpace(cfg.Tasks[i].Model) == "" && model != "" {
cfg.Tasks[i].Model = model
}
cfg.Tasks[i].SkipPermissions = cfg.Tasks[i].SkipPermissions || skipPermissions
}
timeoutSec := resolveTimeout()
@@ -436,6 +444,7 @@ func run() (exitCode int) {
SessionID: cfg.SessionID,
Model: cfg.Model,
ReasoningEffort: cfg.ReasoningEffort,
SkipPermissions: cfg.SkipPermissions,
UseStdin: useStdin,
}

View File

@@ -1587,6 +1587,26 @@ do something`
}
}
func TestParallelParseConfig_SkipPermissions(t *testing.T) {
input := `---TASK---
id: task-1
skip_permissions: true
---CONTENT---
do something`
cfg, err := parseParallelConfig([]byte(input))
if err != nil {
t.Fatalf("parseParallelConfig() unexpected error: %v", err)
}
if len(cfg.Tasks) != 1 {
t.Fatalf("expected 1 task, got %d", len(cfg.Tasks))
}
task := cfg.Tasks[0]
if !task.SkipPermissions {
t.Fatalf("SkipPermissions = %v, want true", task.SkipPermissions)
}
}
func TestParallelParseConfig_EmptySessionID(t *testing.T) {
input := `---TASK---
id: task-1
@@ -4014,6 +4034,30 @@ do two`)
}
})
t.Run("parallelSkipPermissions", func(t *testing.T) {
defer resetTestHooks()
cleanupHook = func() {}
cleanupLogsFn = func() (CleanupStats, error) { return CleanupStats{}, nil }
t.Setenv("CODEAGENT_SKIP_PERMISSIONS", "false")
runCodexTaskFn = func(task TaskSpec, timeout int) TaskResult {
if !task.SkipPermissions {
return TaskResult{TaskID: task.ID, ExitCode: 1, Error: "SkipPermissions not propagated"}
}
return TaskResult{TaskID: task.ID, ExitCode: 0, Message: "ok"}
}
stdinReader = strings.NewReader(`---TASK---
id: only
backend: claude
---CONTENT---
do one`)
os.Args = []string{"codeagent-wrapper", "--parallel", "--skip-permissions"}
if code := run(); code != 0 {
t.Fatalf("run exit = %d, want 0", code)
}
})
t.Run("parallelErrors", func(t *testing.T) {
defer resetTestHooks()
cleanupLogsFn = func() (CleanupStats, error) { return CleanupStats{}, nil }