diff --git a/cmd/task/main.go b/cmd/task/main.go index d91c303a..196d85a5 100644 --- a/cmd/task/main.go +++ b/cmd/task/main.go @@ -746,7 +746,7 @@ Examples: createCmd.Flags().String("effort", "", "Per-task Claude effort override: low, medium, high, xhigh, max (default: Claude's global default)") createCmd.Flags().BoolP("execute", "x", false, "Queue task for immediate execution") createCmd.Flags().Bool("dangerous", false, "Execute in dangerous mode (alias for --permission-mode dangerous)") - createCmd.Flags().String("permission-mode", "", "Permission mode: default (prompt), auto (auto-accept edits), dangerous (skip all). Defaults to the project's setting") + createCmd.Flags().String("permission-mode", "", "Permission mode: default (prompt), accept-edits (auto-accept file edits, still prompt for risky actions; alias: auto), dangerous (skip all). Defaults to the project's setting") createCmd.Flags().String("tags", "", "Task tags (comma-separated)") createCmd.Flags().Bool("pinned", false, "Pin the task to the top of its column") createCmd.Flags().StringP("branch", "b", "", "Existing branch to checkout for worktree (e.g., fix/ui-overflow)") @@ -1602,13 +1602,13 @@ Examples: case db.PermissionModeDangerous: msg += " (dangerous mode)" case db.PermissionModeAuto: - msg += " (auto mode)" + msg += " (accept-edits mode)" } fmt.Println(successStyle.Render(msg)) }, } executeCmd.Flags().Bool("dangerous", false, "Execute in dangerous mode (alias for --permission-mode dangerous)") - executeCmd.Flags().String("permission-mode", "", "Override permission mode: default (prompt), auto (auto-accept edits), dangerous (skip all)") + executeCmd.Flags().String("permission-mode", "", "Override permission mode: default (prompt), accept-edits (auto-accept file edits; alias: auto), dangerous (skip all)") rootCmd.AddCommand(executeCmd) statusCmd := &cobra.Command{ @@ -2532,7 +2532,7 @@ Examples: projectsCreateCmd.Flags().StringP("color", "c", "", "Hex color for display (e.g., #61AFEF)") projectsCreateCmd.Flags().StringP("aliases", "a", "", "Comma-separated aliases for lookup") projectsCreateCmd.Flags().String("claude-config-dir", "", "Override CLAUDE_CONFIG_DIR for this project") - projectsCreateCmd.Flags().String("permission-mode", "", "Default permission mode for tasks: default (prompt), auto (auto-accept edits), dangerous (skip all)") + projectsCreateCmd.Flags().String("permission-mode", "", "Default permission mode for tasks: default (prompt), accept-edits (auto-accept file edits; alias: auto), dangerous (skip all)") projectsCreateCmd.Flags().Bool("no-git", false, "Disable git worktrees (for non-git projects)") projectsCreateCmd.Flags().Bool("json", false, "Output in JSON format") projectsCreateCmd.MarkFlagRequired("path") @@ -2590,7 +2590,7 @@ Examples: projectsUpdateCmd.Flags().StringP("aliases", "a", "", "Comma-separated aliases for lookup") projectsUpdateCmd.Flags().String("claude-config-dir", "", "Override CLAUDE_CONFIG_DIR for this project") projectsUpdateCmd.Flags().String("context", "", "Cached project context summary") - projectsUpdateCmd.Flags().String("permission-mode", "", "Default permission mode for tasks: default (prompt), auto (auto-accept edits), dangerous (skip all)") + projectsUpdateCmd.Flags().String("permission-mode", "", "Default permission mode for tasks: default (prompt), accept-edits (auto-accept file edits; alias: auto), dangerous (skip all)") projectsUpdateCmd.Flags().Bool("no-git", false, "Disable git worktrees (for non-git projects)") projectsUpdateCmd.Flags().Bool("git", false, "Enable git worktrees (default)") projectsUpdateCmd.Flags().Bool("json", false, "Output in JSON format") @@ -5700,7 +5700,7 @@ func updateProjectCLI(currentName, newName, path, instructions, color, aliases, if permissionMode != "" { normalized := db.NormalizePermissionMode(permissionMode) if normalized == "" { - fmt.Fprintln(os.Stderr, errorStyle.Render("Error: invalid permission mode (use default, auto, or dangerous)")) + fmt.Fprintln(os.Stderr, errorStyle.Render("Error: invalid permission mode (use default, accept-edits, or dangerous)")) os.Exit(1) } project.DefaultPermissionMode = normalized diff --git a/internal/db/permission_mode_test.go b/internal/db/permission_mode_test.go index 1f7bbda3..4343ca3e 100644 --- a/internal/db/permission_mode_test.go +++ b/internal/db/permission_mode_test.go @@ -8,12 +8,16 @@ import ( func TestNormalizePermissionMode(t *testing.T) { cases := map[string]string{ - "auto": PermissionModeAuto, - "dangerous": PermissionModeDangerous, - "default": PermissionModeDefault, - "prompt": PermissionModeDefault, - "": "", - "bogus": "", + "auto": PermissionModeAuto, // legacy alias, kept for back-compat + "accept-edits": PermissionModeAuto, // unambiguous spelling normalizes to the canonical value + "accept_edits": PermissionModeAuto, + "acceptEdits": PermissionModeAuto, // Claude Code's own spelling + " Auto ": PermissionModeAuto, // trimmed + case-insensitive + "dangerous": PermissionModeDangerous, + "default": PermissionModeDefault, + "prompt": PermissionModeDefault, + "": "", + "bogus": "", } for in, want := range cases { if got := NormalizePermissionMode(in); got != want { diff --git a/internal/db/tasks.go b/internal/db/tasks.go index f21a30e5..e89c4366 100644 --- a/internal/db/tasks.go +++ b/internal/db/tasks.go @@ -32,7 +32,7 @@ type Task struct { PRNumber int // Pull request number (if associated with a PR) PRInfoJSON string // Cached PR state as JSON (state, checks, mergeable, etc.) DangerousMode bool // Whether task is running in dangerous mode (--dangerously-skip-permissions). Kept for backward compat; PermissionMode is authoritative. - PermissionMode string // Permission mode for execution: "default" (prompt), "auto" (acceptEdits), "dangerous" (skip permissions). Empty falls back to DangerousMode/global default. + PermissionMode string // Permission mode for execution: "default" (prompt), "auto"/"accept-edits" (Claude's acceptEdits — auto-accept file edits, still prompts for risky actions), "dangerous" (skip permissions). Empty falls back to DangerousMode/global default. Pinned bool // Whether the task is pinned to the top of its column Tags string // Comma-separated tags for categorization (e.g., "customer-support,email,influence-kit") SourceBranch string // Existing branch to checkout for worktree (e.g., "fix/ui-overflow") instead of creating new branch @@ -68,24 +68,43 @@ func IsInProgress(status string) bool { } // Permission modes control how the underlying agent handles permission prompts. +// +// IMPORTANT: the stored value "auto" is historical — it predates Claude Code's +// own "auto mode" (the agentic permission flow gated by --enable-auto-mode). +// In TaskYou, "auto" means Claude Code's ACCEPT-EDITS mode (--permission-mode +// acceptEdits): auto-accept file edits while still prompting for risky actions. +// It is NOT Claude Code's --enable-auto-mode. To avoid that overloaded word, +// surface this mode to users and agents as "accept edits" / "accept-edits"; +// the "auto" value is kept only for back-compat (and accepted as an alias). const ( // PermissionModeDefault prompts for permissions (the historical default). PermissionModeDefault = "default" - // PermissionModeAuto auto-accepts file edits but still gates risky actions - // (Claude's --permission-mode acceptEdits). This is the "auto mode" most - // users want: handles ~99% of permission prompts without the risk of - // fully bypassing permissions. + // PermissionModeAuto maps to Claude Code's accept-edits mode + // (--permission-mode acceptEdits): auto-accept file edits but still gate + // risky actions. This is the low-friction mode most users want — it handles + // ~99% of permission prompts without fully bypassing permissions. Despite + // the stored "auto" value, this is NOT Claude Code's --enable-auto-mode; + // display it as "accept edits" (see PermissionModeAutoLabel). PermissionModeAuto = "auto" // PermissionModeDangerous bypasses all permission checks // (Claude's --dangerously-skip-permissions). PermissionModeDangerous = "dangerous" ) +// PermissionModeAutoLabel is the unambiguous human-facing name for +// PermissionModeAuto. We deliberately avoid the word "auto" in UI and prompts +// because it collides with Claude Code's separate "auto mode" +// (--enable-auto-mode), which TaskYou does not currently expose. +const PermissionModeAutoLabel = "accept-edits" + // NormalizePermissionMode coerces a raw value into a known permission mode. -// "prompt" and "" are treated as default; unknown values return "". +// "prompt" and "" are treated as default. The unambiguous "accept-edits" name +// (and its variants, plus Claude's own "acceptEdits") all map to the canonical +// PermissionModeAuto value, so callers can use the clearer spelling while old +// "auto" values keep working. Unknown values return "". func NormalizePermissionMode(mode string) string { - switch mode { - case PermissionModeAuto: + switch strings.ToLower(strings.TrimSpace(mode)) { + case PermissionModeAuto, PermissionModeAutoLabel, "accept_edits", "acceptedits": return PermissionModeAuto case PermissionModeDangerous: return PermissionModeDangerous @@ -124,7 +143,7 @@ func (t *Task) IsDangerous() bool { return t.EffectivePermissionMode() == PermissionModeDangerous } -// IsAutoPermission reports whether the task runs in auto (acceptEdits) mode. +// IsAutoPermission reports whether the task runs in accept-edits (acceptEdits) mode. func (t *Task) IsAutoPermission() bool { return t.EffectivePermissionMode() == PermissionModeAuto } diff --git a/internal/executor/claude_executor.go b/internal/executor/claude_executor.go index 63daca0c..fe44bc89 100644 --- a/internal/executor/claude_executor.go +++ b/internal/executor/claude_executor.go @@ -104,7 +104,7 @@ func (c *ClaudeExecutor) ResumeProcess(taskID int64) bool { // BuildCommand returns the shell command to start an interactive Claude session. func (c *ClaudeExecutor) BuildCommand(task *db.Task, sessionID, prompt string) string { - // Build permission mode flag (dangerous, auto/acceptEdits, or none) + // Build permission mode flag (dangerous, accept-edits/acceptEdits, or none) dangerousFlag := claudePermissionFlag(task) // Build per-task effort override flag (empty = use Claude's global default) diff --git a/internal/executor/permission_flag_test.go b/internal/executor/permission_flag_test.go index 3349116f..b27f9f6b 100644 --- a/internal/executor/permission_flag_test.go +++ b/internal/executor/permission_flag_test.go @@ -18,6 +18,7 @@ func TestClaudePermissionFlag(t *testing.T) { {"default empty", &db.Task{}, ""}, {"explicit default", &db.Task{PermissionMode: db.PermissionModeDefault}, ""}, {"auto", &db.Task{PermissionMode: db.PermissionModeAuto}, "--permission-mode acceptEdits "}, + {"accept-edits alias", &db.Task{PermissionMode: "accept-edits"}, "--permission-mode acceptEdits "}, {"dangerous", &db.Task{PermissionMode: db.PermissionModeDangerous}, "--dangerously-skip-permissions "}, {"legacy dangerous bool", &db.Task{DangerousMode: true}, "--dangerously-skip-permissions "}, } diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 655785a6..fda076c7 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -242,8 +242,8 @@ func (s *Server) handleRequest(req *jsonRPCRequest) { }, "permission_mode": map[string]interface{}{ "type": "string", - "description": "Permission mode for execution: 'default' (prompt), 'auto' (auto-accept edits), or 'dangerous' (skip all prompts). Defaults to the project's configured default.", - "enum": []string{"default", "auto", "dangerous"}, + "description": "Permission mode for execution: 'default' (prompt for each permission), 'accept-edits' (Claude Code's acceptEdits / --permission-mode acceptEdits: auto-accept file edits but still prompt for risky actions — this is NOT Claude Code's separate 'auto mode' from --enable-auto-mode), or 'dangerous' (skip all prompts / --dangerously-skip-permissions). The legacy value 'auto' is still accepted and means the same as 'accept-edits'. Defaults to the project's configured default.", + "enum": []string{"default", "accept-edits", "dangerous"}, }, }, "required": []string{"title"}, diff --git a/internal/ui/app.go b/internal/ui/app.go index 72a8d214..dda0e0c7 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -2900,7 +2900,7 @@ func (m *AppModel) updateNewTaskForm(msg tea.Msg) (tea.Model, tea.Cmd) { Options( huh.NewOption("No — save to backlog", "no"), huh.NewOption("Yes — execute now", "yes"), - huh.NewOption("Yes — execute in auto mode", "auto"), + huh.NewOption("Yes — execute in accept-edits mode", "auto"), huh.NewOption("Yes — execute in dangerous mode", "dangerous"), ). Value(&m.queueValue), diff --git a/internal/ui/detail.go b/internal/ui/detail.go index 34316335..b04afa9a 100644 --- a/internal/ui/detail.go +++ b/internal/ui/detail.go @@ -2358,7 +2358,9 @@ func (m *DetailModel) renderHeader() string { meta.WriteString(" ") } - // Auto mode badge (acceptEdits) for active tasks. + // Accept-edits badge (Claude's acceptEdits mode) for active tasks. Labeled + // "ACCEPT EDITS" rather than "AUTO" so it isn't confused with Claude Code's + // separate auto mode (--enable-auto-mode). if t.IsAutoPermission() && (t.Status == db.StatusProcessing || t.Status == db.StatusBlocked) { var autoStyle lipgloss.Style if m.focused { @@ -2373,7 +2375,7 @@ func (m *DetailModel) renderHeader() string { Background(dimmedBg). Foreground(dimmedFg) } - meta.WriteString(autoStyle.Render("AUTO")) + meta.WriteString(autoStyle.Render("ACCEPT EDITS")) meta.WriteString(" ") } diff --git a/internal/ui/kanban.go b/internal/ui/kanban.go index 2c477047..1af324e4 100644 --- a/internal/ui/kanban.go +++ b/internal/ui/kanban.go @@ -1165,7 +1165,7 @@ func (k *KanbanBoard) renderTaskCard(task *db.Task, width int, isSelected bool, indicators = append(indicators, dangerStyle.Render("●")) } } - // Auto mode indicator (green dot) for active tasks running in auto/acceptEdits mode. + // Accept-edits indicator (green dot) for active tasks running in Claude's acceptEdits mode. if task.IsAutoPermission() && (task.Status == db.StatusProcessing || task.Status == db.StatusBlocked) { if isSelected { indicators = append(indicators, "●") diff --git a/internal/ui/settings.go b/internal/ui/settings.go index 54eef64f..8b4f96cc 100644 --- a/internal/ui/settings.go +++ b/internal/ui/settings.go @@ -278,8 +278,8 @@ func (m *SettingsModel) showProjectForm(project *db.Project) (*SettingsModel, te m.projectFormUseWorktrees = project.UseWorktrees // Default permission mode. Use the project's explicit setting when present, - // otherwise pre-select the effective default (auto) so the form mirrors the - // mode tasks will actually run in. + // otherwise pre-select the effective default (accept-edits) so the form + // mirrors the mode tasks will actually run in. m.projectFormPermissionMode = db.NormalizePermissionMode(project.DefaultPermissionMode) if m.projectFormPermissionMode == "" { m.projectFormPermissionMode = project.EffectiveDefaultPermissionMode() @@ -342,9 +342,9 @@ func (m *SettingsModel) showProjectForm(project *db.Project) (*SettingsModel, te huh.NewSelect[string](). Key("permission_mode"). Title("Default Permission Mode"). - Description("How new tasks handle permissions. Auto handles ~99% without prompting."). + Description("How new tasks handle permissions. Accept Edits handles ~99% without prompting."). Options( - huh.NewOption("Auto — auto-accept edits (recommended)", db.PermissionModeAuto), + huh.NewOption("Accept Edits — auto-accept file edits, still prompt for risky actions (recommended)", db.PermissionModeAuto), huh.NewOption("Prompt — ask for each permission", db.PermissionModeDefault), huh.NewOption("Dangerous — skip all permission checks", db.PermissionModeDangerous), ).