Skip to content

fix(win): kill orphan setup-runner.cmd before worktree delete - #10637

Closed
innocarpe wants to merge 4 commits into
stablyai:mainfrom
innocarpe:fix/windows-setup-runner-orphan-delete
Closed

innocarpe wants to merge 4 commits into
stablyai:mainfrom
innocarpe:fix/windows-setup-runner-orphan-delete

Conversation

@innocarpe

@innocarpe innocarpe commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Before worktree delete on Windows, force-kill setup-runner.cmd processes whose CommandLine references the worktree’s private gitdir path.
  • Runs after PTY teardown inside killAllProcessesForWorktree so git worktree remove / rmdir are not blocked by orphan handles.

Fixes

Closes #10629

Test plan

  • windows-worktree-setup-runner-kill + worktree-teardown unit tests
  • Manual Windows: create worktree with setup, delete from Orca — directory fully removed

ELI5

Orphan setup-runner.cmd processes could hold Windows worktree directories open so delete failed. Orca force-kills those orphans tied to the worktree's gitdir before removal.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@innocarpe, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fa92e05-21da-4fbb-95c7-4edc98018376

📥 Commits

Reviewing files that changed from the base of the PR and between 911968d and adacfa6.

📒 Files selected for processing (2)
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/windows-worktree-setup-runner-kill.test.ts
📝 Walkthrough

Walkthrough

Adds Windows-specific detection and termination for setup-runner.cmd process trees associated with worktrees. The implementation resolves linked-worktree gitdir paths, matches process command lines, enumerates Windows processes, deduplicates PIDs, and handles termination failures. Worktree teardown now awaits this cleanup after PTY processing and propagates its deadline. Tests cover path handling, matching, process termination, deadline gating, logging, and teardown integration.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the fix and tests but omits the required Screenshots, AI Review Report, Security Audit, and Notes sections. Add the required sections and document visual impact, AI review scope, security risks, cross-platform checks, and platform-specific notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: terminating orphaned setup-runner.cmd processes before worktree deletion.
Linked Issues check ✅ Passed The changes implement deadline-bounded, path-anchored termination of orphaned setup-runner process trees before Windows worktree deletion, with regression tests for issue #10629.
Out of Scope Changes check ✅ Passed The new termination module, teardown integration, and tests directly support the linked issue and stated pull request objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/windows-worktree-setup-runner-kill.test.ts (1)

88-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the relative-gitdir branch.

resolveWorktreeGitDirPath Line 50 (resolve(trimmed, raw)) is the one untested path here; Git does write relative gitdir: entries.

src/main/runtime/worktree-teardown.test.ts (1)

849-860: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert ordering, not just invocation.

The regression in #10629 is specifically that the sweep must run after PTY teardown. This test would still pass if the call were hoisted to the top of killAllProcessesForWorktree. Comparing mock.invocationCallOrder against the provider shutdown/stop spy would pin the contract.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93100688-e7ff-4b2e-847d-10d3dd8ba307

📥 Commits

Reviewing files that changed from the base of the PR and between c4b7aed and eb6e4bd716726026d617711ea10e4f3ed1785cba.

📒 Files selected for processing (4)
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/main/windows-worktree-setup-runner-kill.test.ts
  • src/main/windows-worktree-setup-runner-kill.ts

Comment thread src/main/runtime/worktree-teardown.ts Outdated
Comment thread src/main/windows-worktree-setup-runner-kill.ts Outdated
@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (3d481a979)

Address CodeRabbit: bound setup-runner sweep with settleBeforeDeadline; require path anchors (no basename-only same-name kill)

@nwparker nwparker added the bug Something isn't working label Jul 27, 2026
@AmethystLiang
AmethystLiang requested a review from OrcaWin July 28, 2026 16:50
@innocarpe
innocarpe force-pushed the fix/windows-setup-runner-orphan-delete branch from 3d481a9 to ee51018 Compare August 6, 2026 02:35
@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (ee51018be7)

Rebase onto latest upstream/main: keep unstopped-PTY verification path; terminate Windows setup-runner orphans after PTY stop (#10629).

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7168fd65-101d-43aa-86cf-16f37a78085c

📥 Commits

Reviewing files that changed from the base of the PR and between 3838f6f and ee51018be7ce73bd80a4cecb83176b04bfacc5f1.

📒 Files selected for processing (4)
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/main/windows-worktree-setup-runner-kill.test.ts
  • src/main/windows-worktree-setup-runner-kill.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/runtime/worktree-teardown.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/windows-worktree-setup-runner-kill.test.ts

Comment thread src/main/windows-worktree-setup-runner-kill.ts
…ai#10629)

On Windows, cmd.exe processes left running setup-runner.cmd from a
worktree's private gitdir keep directory handles open so git worktree
remove / rmdir fail with Permission denied. After PTY teardown, scan
process CommandLines and taskkill /T /F matching setup-runner trees.
…#10629)

Address CodeRabbit: race the setup-runner kill with the teardown deadline
via settleBeforeDeadline, and drop basename-only linked-worktree matching
so same-named worktrees under other repos are not force-killed.
@innocarpe
innocarpe force-pushed the fix/windows-setup-runner-orphan-delete branch from ee51018 to 911968d Compare August 6, 2026 03:53
@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (911968d97f)

Addressed CodeRabbit: the Windows setup-runner sweep now enforces the absolute teardown deadline across gitdir resolution, process enumeration, and process-tree kill aggregation. Added regression coverage for each slow stage; related tests (51 passed), typecheck, changed-file lint, React Doctor, and Oxfmt pass after rebasing onto upstream/main 81e7a2d.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (adacfa6bd3)

Addressed CodeRabbit nitpicks: added coverage for relative gitdir entries and asserted that the setup-runner sweep is invoked after PTY shutdown. Related tests (52 passed), typecheck, changed-file lint, React Doctor, and Oxfmt pass after rebasing onto upstream/main 81e7a2d.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (7)
src/main/windows-worktree-setup-runner-kill.ts (3)

95-104: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Paths that contain spaces are truncated by this pattern.

[^\s"']*? stops at the first space. Windows worktree paths often contain spaces, for example C:\Users\Jane Doe\repo\.... For a quoted command line the extractor then yields a partial path that starts after the space, and isPathInsideOrEqual at Line 141 rejects it.

The substring fallback at Lines 149-153 still matches these command lines, so behavior stays correct today. Consider extracting quoted arguments first so the primary path-containment check handles them.


242-257: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The returned count reports attempted kills, not confirmed kills.

Each killTree rejection is swallowed at Line 246, and killedPids is the full targetPids list. The caller logs this value as killed N Windows setup-runner process tree(s) at Line 177. When taskkill fails with access denied, the log reports a kill that did not happen. The test at Line 191 encodes this behavior.

No control flow depends on the count, so this is an observability wording issue. Either count only the settled successes, or change the log text to attempted.


85-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle directory-read error codes before falling back.

readFile on a directory can return EPERM or EACCES as well as EISDIR on Windows. If resolveWorktreeGitDirPath drops those cases, .git main-checkouts can lose their anchor and the sweep falls back to worktree-path-only matching. Accept the alternate error codes to keep the fallback reliable.

♻️ Proposed fix
   } catch (error) {
     // Why: main checkouts store objects under a `.git` directory, not a gitdir file.
-    if ((error as NodeJS.ErrnoException | undefined)?.code === 'EISDIR') {
+    const code = (error as NodeJS.ErrnoException | undefined)?.code
+    if (code === 'EISDIR' || code === 'EPERM' || code === 'EACCES') {
       return join(trimmed, '.git')
     }
     return null
   }
src/main/windows-worktree-setup-runner-kill.test.ts (4)

45-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These two tests are identical; replace the second one.

The test at Lines 55-63 passes the same command line, the same worktreePath, and the same pathAnchors as the test at Lines 45-53, and asserts the same result. Its title claims a separate scenario, so it adds no coverage and misleads a reader.

Retarget it at the substring fallback branch in commandLineTargetsWorktreeSetupRunner (implementation Lines 149-153). No current test reaches that branch, because every positive case also satisfies the path-containment branch at Line 141.

♻️ Proposed replacement
-  it('matches linked-worktree setup-runner when the command references the gitdir anchor', () => {
-    expect(
-      commandLineTargetsWorktreeSetupRunner(
-        'C:\\Windows\\system32\\cmd.exe /c D:/repo/.git/worktrees/feature/orca/setup-runner.cmd',
-        worktreePath,
-        ['D:/repo/.git/worktrees/feature']
-      )
-    ).toBe(true)
-  })
+  it('matches via the substring fallback when the runner path is not drive-qualified', () => {
+    expect(
+      commandLineTargetsWorktreeSetupRunner(
+        `cmd.exe /c setup-runner.cmd --worktree ${worktreePath}`,
+        worktreePath
+      )
+    ).toBe(true)
+  })

100-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a relative gitdir: value.

resolveWorktreeGitDirPath resolves a relative gitdir against the worktree path at implementation Line 84. Git writes relative gitdir pointers when a worktree is created with --relative-paths, so this branch is reachable. No test pins it.

♻️ Proposed additional test
   it('returns null when .git is missing', async () => {
+    // placeholder anchor for the added test below

Add this test inside the resolveWorktreeGitDirPath describe block:

it('resolves a relative gitdir against the worktree path', async () => {
  const readFileImpl = vi.fn(
    async () => 'gitdir: ../../.git/worktrees/feature\n'
  ) as unknown as typeof fsPromises.readFile
  await expect(
    resolveWorktreeGitDirPath(join('/repo', 'workspaces', 'feature'), { readFileImpl })
  ).resolves.toBe(resolve(join('/repo', 'workspaces', 'feature'), '../../.git/worktrees/feature'))
})

Import resolve alongside join at Line 1.


217-230: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inject resolveGitDirPath to remove the real filesystem read.

This test omits pathAnchors, so terminateWindowsSetupRunnersForWorktree calls the real resolveWorktreeGitDirPath, which reads D:\orca\workspaces\repo\feature\.git from disk. The assertion still holds on macOS, Linux, and Windows because enumeration throws regardless. Injecting the dependency keeps the test hermetic and faster.

♻️ Proposed fix
       terminateWindowsSetupRunnersForWorktree('D:\\orca\\workspaces\\repo\\feature', {
         platform: 'win32',
+        resolveGitDirPath: async () => null,
         listProcessRows,
         killTree
       })

331-345: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the console.info spy in a finally block or an afterEach hook.

info.mockRestore() runs only when every assertion between Lines 333 and 344 passes. If one fails, the spy stays installed and suppresses console.info for the remaining tests in this file. That turns one failure into cascading, hard-to-diagnose failures.

♻️ Proposed fix
 describe('terminateWindowsSetupRunnersForWorktreeId', () => {
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })
+
   it('resolves the worktree path and kills matching runners', async () => {
     expect(info).toHaveBeenCalledWith(
       expect.stringContaining('killed 1 Windows setup-runner process tree(s)')
     )
-    info.mockRestore()
   })

Add afterEach to the Vitest import at Line 3.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fec3029e-1424-4002-8994-fbfcbfe6b4cd

📥 Commits

Reviewing files that changed from the base of the PR and between 81e7a2d and 911968d.

📒 Files selected for processing (4)
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/main/windows-worktree-setup-runner-kill.test.ts
  • src/main/windows-worktree-setup-runner-kill.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/runtime/worktree-teardown.ts
  • src/main/runtime/worktree-teardown.test.ts

@innocarpe

Copy link
Copy Markdown
Contributor Author

Closing because #10629 was marked completed.

I am not keeping a conflicting Windows setup-runner teardown branch open against a completed issue. Happy to reopen if a current-main reproduction still shows orphaned setup-runner.cmd after worktree delete.

@innocarpe innocarpe closed this Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: deleting a worktree leaves orphaned setup-runner processes that lock the directory

3 participants