feat(settings): add create-worktree-by-default toggle for new tasks and fix run restart after stop intent#1329
Conversation
…t after stop intent
|
@isEmmanuelOlowe is attempting to deploy a commit to the General Action Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis PR adds a Key changes:
Issues found:
Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| src/main/services/TaskLifecycleService.ts | Adds !this.stopIntents.has(taskId) guard to allow run restart during an active stop intent, but fails to clear the lingering stop intent before spawning the new process — causing the new process's exit to be misclassified as 'idle' instead of 'succeeded'. |
| src/main/settings.ts | Adds createWorktreeByDefault: boolean to the AppSettings interface, sets a sensible default of true, and normalises it correctly in normalizeSettings. Pattern is consistent with existing boolean settings. |
| src/renderer/components/SettingsPage.tsx | Imports and inserts CreateWorktreeByDefaultRow between the auto-approve and auto-trust-worktrees rows. Placement is semantically appropriate; change is minimal and correct. |
| src/renderer/components/TaskModal.tsx | Reads createWorktreeByDefault from async-loaded settings and applies it via setUseWorktree. The hardcoded setUseWorktree(true) synchronous reset on modal open will cause a visible flicker for users who have the setting disabled. |
| src/renderer/components/TaskSettingsRows.tsx | New CreateWorktreeByDefaultRow component follows the exact same structure as peer rows (AutoApproveByDefaultRow, AutoTrustWorktreesRow). No issues. |
| src/renderer/hooks/useTaskSettings.ts | Extends TaskSettingsModel with createWorktreeByDefault field and updateCreateWorktreeByDefault handler. Default fallback (?? true) matches the backend default. No issues. |
Sequence Diagram
sequenceDiagram
participant User
participant TaskModal
participant SettingsStore
participant TaskLifecycleService
User->>TaskModal: Open "New Task" modal
TaskModal->>SettingsStore: rpc.appSettings.get()
SettingsStore-->>TaskModal: { createWorktreeByDefault: bool }
TaskModal->>TaskModal: setUseWorktree(createWorktreeByDefault)
User->>TaskModal: Submit task (useWorktree=false)
TaskModal->>TaskLifecycleService: createTask(useWorktree=false)
Note over TaskLifecycleService: Task runs on shared branch
User->>TaskLifecycleService: stopRun(taskId)
TaskLifecycleService->>TaskLifecycleService: stopIntents.add(taskId)
TaskLifecycleService->>TaskLifecycleService: SIGTERM → old process
User->>TaskLifecycleService: startRun(taskId) [restart]
TaskLifecycleService->>TaskLifecycleService: stopIntents.has(taskId)=true → skip guard
TaskLifecycleService->>TaskLifecycleService: spawn new process
Note over TaskLifecycleService: ⚠️ stopIntents NOT cleared
TaskLifecycleService->>TaskLifecycleService: old process exits → early return (no cleanup)
TaskLifecycleService->>TaskLifecycleService: new process exits (code=0)
TaskLifecycleService->>TaskLifecycleService: wasStopped=true (stale intent!) → status='idle' ❌
Comments Outside Diff (1)
-
src/renderer/components/TaskModal.tsx, line 219 (link)Hardcoded reset causes flicker when setting is
falseOn every modal open,
setUseWorktree(true)is called unconditionally before the async settings fetch resolves. If the user has savedcreateWorktreeByDefault: false, the toggle will briefly flash as checked (true) before being corrected once therpc.appSettings.get()promise resolves.The other two async-loaded booleans (
autoApprove,autoGenerateName) start from safefalse/truedefaults that match their actual saved values for most users, so the flicker is rarely visible. ForuseWorktree, users who disabled the default will see the flash every time they open the modal.Consider initialising the state from the already-available
useAppSettingscontext (used byuseTaskSettings) instead of a hard-coded literal, or at minimum drop the synchronous reset line so the previous value is preserved until the fetch completes.→ could be removed or replaced with the value from the synchronously available settings context if the context is accessible here.
Last reviewed commit: 97cfff9
| if ( | ||
| existing && | ||
| existing.exitCode === null && | ||
| !existing.killed && | ||
| !this.stopIntents.has(taskId) | ||
| ) { | ||
| return { ok: true, skipped: true }; | ||
| } |
There was a problem hiding this comment.
Stale stop intent corrupts new run's exit status
When startRun is called while a stop intent is active (e.g. the user clicks "restart" immediately after clicking "stop"), the new guard correctly bypasses the skip check and spawns a fresh process. However, the stop intent for taskId is never cleared before the new child is registered in runProcesses.
Here is what happens step by step:
stopRunis called →stopIntents.add(taskId), SIGTERM sent to old process.startRunis called before the old process exits.startRunInternalpasses the new guard (stop intent present), spawns a new child, and callsthis.runProcesses.set(taskId, newChild).- Old process exits →
runProcesses.get(taskId) !== oldChild, so the handler returns early without callingthis.stopIntents.delete(taskId). - New process runs to completion (exit code 0).
- New process's
exithandler fires:wasStopped = this.stopIntents.has(taskId)evaluates totrue(the leftover stop intent), sostatusis set to'idle'instead of'succeeded'.
The fix is to clear the stop intent immediately after the guard is bypassed, before spawning the new process:
// Clear any residual stop intent so the new process's exit is not misclassified.
if (this.stopIntents.has(taskId)) {
this.stopIntents.delete(taskId);
}
const state = this.ensureState(taskId);
state.run = {
status: 'running',
...
|
I see a PR from the SSH King I am happy |
Without this, restarting a run while a stop is in flight leaves the old stop intent in place. When the new process exits, it sees the leftover intent and misreports its status as 'idle' instead of 'succeeded'/'failed'. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Added a one-line fix for the stale stop-intent bug in TaskLifecycleService.ts: when restarting a run while a stop is in flight, the old stop intent was never cleared before spawning the new process. |
Summary
This PR adds a new task setting to reduce friction when creating many parallel tasks on the same branch.
Users can now disable worktree creation by default from Settings, and the New Task modal respects that saved preference.
It also includes a small lifecycle fix so a run can restart correctly while a stop intent is active.
Why
Some users working with capable parallel agents (for example Kimi 2.5, GPT-5.*, Claude) prefer creating many simultaneous tasks on a shared branch. This setting makes that workflow one-click instead of repeatedly disabling worktree creation.
Fixes
N/A
Snapshot
N/A (settings toggle + behavior change)
Type of change
Mandatory Tasks
Checklist
pnpm run format)pnpm run lint)