Skip to content

[Kill Process] Make app grouping work on Windows - #30983

Merged
raycastbot merged 7 commits into
raycast:mainfrom
Just-Me-22:fix/kill-process-windows-grouping
Sep 16, 2026
Merged

raycastbot merged 7 commits into
raycast:mainfrom
Just-Me-22:fix/kill-process-windows-grouping

Conversation

@Just-Me-22

@Just-Me-22 Just-Me-22 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Description

Closes #26386.

App Grouping does nothing on Windows. groupRelatedProcesses keys off the outer .app bundle path, and no Windows path has one, so every process falls straight through to the ungrouped list. Against 434 live processes on my machine, exactly 0 matched that regex, which is why the reporter sees Spotify running as eight separate rows with the toggle on.

Windows now groups by executable path. On the same machine that takes the list from 434 rows to 250, with Spotify's 8 processes collapsing into one row the way the report expects.

Two things came out of testing that changed the shape of this.

Grouped processes on Windows are usually siblings, not a tree. Killing a group ran taskkill /T on the group's main process, which is right on macOS where a bundle's processes really are descendants of the app. On Windows I measured how many group members that actually reaches:

Group Size Killed by /T Survives
Spotify 8 8 0
Dia 21 21 0
DiscordCanary 6 6 0
conhost 29 1 28
cmd 25 3 22
node 22 1 21
svchost 9 1 8

117 of 187 grouped children would have survived, while the UI removed their rows anyway, since it drops [id, ...childProcessIds] from the list on success. Apps that spawn their own helpers are fine. Utility processes are siblings under different parents, so no member is an ancestor of the others. The group kill now names every process it is removing, which is exact regardless of the tree shape. I verified taskkill /F /T /PID a /PID b /PID c on three sibling processes: all three terminated.

The Windows process list had no parent ids. parseWindowsProcesses hardcoded pid: 0, so findMainProcess could not tell which process owned a group and would have picked an arbitrary one. Get-Process does not report a parent at all, so the list now comes from Win32_Process, which does. Costs are 472ms before and 621ms after on my machine. Building the parent map from a separate Get-CimInstance call and joining it to Get-Process was much worse at 2318ms, so the single query is the cheaper of the two ways to get this.

Swapping the source needed two adjustments to keep the data identical:

  • Win32_Process reports Spotify.exe where Get-Process reports Spotify, and that name is fed to Get-Process -Name by getKillAllCommand, which wants it without the extension. The script strips it. I compared both sources across all 435 processes: 0 name mismatches after stripping.
  • Win32_Process sometimes returns the \\?\ extended-length prefix on a path. 4 of 435 differed, all conhost.exe. Left alone that would have split conhost into two groups, so the parser normalises it.

One judgement call worth your view: grouping strictly by executable means conhost.exe becomes a single row of 29, and svchost.exe a single row of 9. It is consistent and the kill is now honest about what it removes, but if you would rather leave system paths ungrouped I am happy to add that.

Checklist

@Just-Me-22
Just-Me-22 requested a review from 0xdhrv as a code owner September 10, 2026 23:54
@raycastbot raycastbot added extension fix / improvement Label for PRs with extension's fix improvements extension: kill-process Issues related to the kill-process extension AI Extension platform: macOS platform: Windows labels Sep 10, 2026
@raycastbot

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! 🎉

🔔 @rolandleth @crazyones110 @zhenpewu @Saafo @erics118 @xilopaint @validatedev @nmder @mateusbadalotti @jomifepe @0xdhrv you might want to have a look.

You can use this guide to learn how to check out the Pull Request locally in order to test it.

📋 Quick checkout commands
BRANCH="fix/kill-process-windows-grouping"
FORK_URL="https://github.com/Just-Me-22/extensions.git"
EXTENSION_NAME="kill-process"
REPO_NAME="extensions"

git clone -n --depth=1 --filter=tree:0 -b $BRANCH $FORK_URL
cd $REPO_NAME
git sparse-checkout set --no-cone "extensions/$EXTENSION_NAME"
git checkout
cd "extensions/$EXTENSION_NAME"
npm install && npm run dev

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days.

@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Kill Process now groups related Windows processes by executable and parent chain instead of looking for macOS app bundles. Grouped Windows rows also kill every listed process and keep the executable's icon.

  • Windows process data now includes parent IDs and normalized executable paths.
  • Related Windows processes collapse into one row without merging unrelated instances.
  • Group kills target every process in the group.

Confidence Score: 4/5

The code appears safe, but the changelog rule must be met before merge.

The process grouping fix has no accepted code bug. The changelog still breaks an explicit repository rule and must be fixed. Three earlier threads were manually resolved without explanatory replies.

Files Needing Attention: extensions/kill-process/CHANGELOG.md

Important Files Changed

Filename Overview
extensions/kill-process/CHANGELOG.md The entry is at the top, but its title uses a fixed date instead of {PR_MERGE_DATE}.
extensions/kill-process/src/utils/process-grouping.ts Windows groups only same-path processes that share a same-path parent chain, which keeps unrelated roots apart.
extensions/kill-process/src/utils/platform.ts Windows process rows now include parent IDs, normalize long paths, and build group kill commands from numeric process IDs.
extensions/kill-process/src/utils/process.ts Grouped Windows kills name the main process and each grouped child. Other platforms keep the prior tree kill.
extensions/kill-process/test/platform.test.ts Tests cover Windows path cleanup, parent IDs, and group kill commands.
extensions/kill-process/test/process-grouping.test.ts Tests cover Windows helper chains, separate roots, missing paths, and parent cycles.
Prompt To Fix All With AI
### Issue 1
extensions/kill-process/CHANGELOG.md:3
The changelog rule requires new titles to keep `{PR_MERGE_DATE}` so the release job can fill in the real date. This change replaces it with `2026-09-16`. Restore the placeholder.

```suggestion
## [Fix] - {PR_MERGE_DATE}
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (7): Last reviewed commit: "Update CHANGELOG.md" | Re-trigger Greptile

Comment thread extensions/kill-process/src/utils/process-grouping.ts Outdated
Comment thread extensions/kill-process/src/utils/platform.ts
@Just-Me-22

Copy link
Copy Markdown
Contributor Author

Both findings were right, and the first one was worse than the review says. Grouping is on by default and the group kill names every process in the row, so on my machine one node entry covering 22 unrelated processes would have taken out the MCP servers, the dev watchers and everything else sharing that binary. Fixed.

A group is now a process plus the descendants running the same executable, keyed on the topmost ancestor still running it. That is the "relationship that identifies one app instance" rather than an exclusion list, so it needs no list of generic binaries to maintain. Measured over the 436 processes on my machine:

Executable By path By app instance
Spotify 8 8
Dia 20 20
DiscordCanary 6 6
msedgewebview2 28 9, 7, 6, 6
claude 26 22
node 22 not grouped
conhost 29 not grouped
cmd 25 not grouped
svchost 9 not grouped

Apps that spawn their own helpers still collapse into one row. msedgewebview2 correctly separates into the four app instances that own those processes, and claude separates one instance of 22 from four unrelated ones. Generic binaries stop merging strangers.

This also removed a bug I had introduced. The strategy was picked from the host platform, so groupRelatedProcesses took the Windows path even when the input was macOS data. The existing groups app helpers into the main app process test failed on a Windows machine because of it. The bundle path is now checked first and the Windows key is only a fallback, so the strategy follows the data. macOS behaviour is unchanged: a path with no bundle still returns no key there.

On the tests, you were right that this needed coverage and I had missed that the extension already has a suite. Three added to test/process-grouping.test.ts:

✔ groups app helpers into the main app process
✔ windows groups an app with the helpers it spawned
✔ windows keeps unrelated instances of the same executable apart
✔ windows ignores processes with no path and survives a parent cycle

The second covers exactly what you flagged. The third covers a protected process with no path and a parent cycle, since walking ancestors would otherwise be a place to hang. All 7 tests pass, along with ray lint and ray build -e dev.

@pernielsentikaer pernielsentikaer self-assigned this Sep 11, 2026

@pernielsentikaer pernielsentikaer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @Just-Me-22 👋

Thanks for fixing Windows grouping 💪

The path normalization in parseWindowsProcesses breaks extended UNC paths: \\?\UNC\server\share\App.exe becomes relative. Restart kills the process first, then fails to relaunch it.

Could you handle UNC paths separately and restrict ordinary prefix removal to drive paths?

path: (proc.path || "")
  .replace(/^\\\\\?\\UNC\\/i, "\\\\")
  .replace(/^\\\\\?\\(?=[A-Za-z]:\\)/, ""),

A UNC parser fixture would be helpful too.


I’ll convert this PR into a draft after submitting this review. Please press Ready for review when it’s ready and we’ll have a look 😊

@pernielsentikaer
pernielsentikaer marked this pull request as draft September 11, 2026 05:18
@Just-Me-22

Copy link
Copy Markdown
Contributor Author

Good catch, and the consequence is worse than a wrong string. Restart terminates first and relaunches after, so a UNC path that no longer resolves means the app is gone and cannot be brought back. Applied your version:

export function normalizeWindowsPath(path: string): string {
  return path.replace(/^\\\\\?\\UNC\\/i, "\\\\").replace(/^\\\\\?\\(?=[A-Za-z]:\\)/, "");
}

Pulled out into a named function because the lookahead is doing something that is not obvious from the call site, and so the fixture can hit it directly.

The fixture is in test/platform.test.ts and goes through parseWindowsProcesses rather than the helper, so it covers the parsing too:

Input Output
\\?\C:\Program Files\App\App.exe C:\Program Files\App\App.exe
C:\Program Files\App\App.exe unchanged
\\?\UNC\server\share\App.exe \\server\share\App.exe
\\server\share\App.exe unchanged
empty empty

The UNC row fails against the version you reviewed, which is what makes it worth keeping. I added a second test asserting the parent process id survives parsing, since grouping now depends on it and nothing covered that.

All 9 tests pass, along with ray lint and ray build -e dev. Marking it ready for review.

For completeness on the earlier round: grouping now keys on a process plus the descendants running the same executable, so unrelated node and conhost instances no longer merge into one row. Details are in the comment above.

@Just-Me-22
Just-Me-22 marked this pull request as ready for review September 11, 2026 13:33

@pernielsentikaer pernielsentikaer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @Just-Me-22 👋

Thanks for pushing this through both rounds 💪 The instance-based key is a much better answer than an exclusion list, and the UNC fix is applied exactly as asked, with the fixture going through parseWindowsProcesses. On dc4de63 I get 9/9 tests passing, clean ray lint, and a successful ray build -e dist.

One thing before this can merge:

extensions/kill-process/CHANGELOG.md conflicts with main — main added its own [Fix] - 2026-09-14 entry in the same place, so the PR no longer merges. Could you rebase on main and keep your entry below main's? The other seven files merge cleanly, so it's only the changelog.

Two optional notes, neither is a blocker:

  • Grouped Windows rows lose their icon. getFileIcon returns the executable icon for process.type === "app" only, and grouped rows are aggregatedApp, so they show the 🖥️ emoji instead of the app's icon. Adding || process.type === "aggregatedApp" to that Windows check keeps the grouped row looking like the app it replaced.
  • getKillGroupCommand's multi-/PID string has no test, and since the key resolver only falls back to the Windows key when the host is Windows, groupRelatedProcesses returns ungrouped Windows data on a macOS test run — so the aggregation path itself isn't covered. A small fixture for the generated command would be a cheap win if you're up for it.

Thanks again 🙂


I’ll convert this PR into a draft after submitting this review. Please press Ready for review when it’s ready and we’ll have a look 😊

@pernielsentikaer
pernielsentikaer marked this pull request as draft September 16, 2026 12:47
@Just-Me-22
Just-Me-22 marked this pull request as ready for review September 16, 2026 13:04
Grouping keyed off macOS .app bundle paths, which no Windows path has, so
the toggle did nothing there. Windows now groups by executable path.

Grouped processes on Windows are usually siblings rather than a tree, so
killing a group cannot rely on taskkill /T reaching them from the main
process. The group kill names every process it is removing instead.

The process list comes from Win32_Process now, because app grouping needs
ParentProcessId to tell which process owns a group and Get-Process does not
report one.
Keying Windows groups on the executable alone merged unrelated instances of
generic binaries. Twenty-two node processes with different parents became one
row, and because grouping is on by default and the group kill names every
process in the row, killing that row would have taken out twenty-two unrelated
workloads.

A group is now a process plus the descendants running the same executable, so
Spotify and Discord still collapse into one row while node, cmd, conhost and
svchost stay separate.

The bundle path is checked before the Windows key so the strategy follows the
data rather than the host. The existing macOS grouping test failed when run on
Windows before this.
Stripping the extended length prefix turned \?\UNC\server\share\App.exe
into a relative path, so Restart killed the process and then could not find
the executable to launch it again. The UNC form now becomes \server\share
and the plain prefix is only removed ahead of a drive letter.
getFileIcon only returned the executable icon for app processes, so a grouped
row fell back to the generic icon and stopped looking like the app it replaced.

Also covers the group kill command, which had no test.
@Just-Me-22
Just-Me-22 force-pushed the fix/kill-process-windows-grouping branch from e300776 to a5dfa6d Compare September 16, 2026 13:08
@Just-Me-22

Just-Me-22 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Hey @pernielsentikaer

Thank you for waiting on this one. I will admit it turned into a much harder fix than I expected when I picked up the issue, but here is everything you asked for. I had a go at the optional bits too, so it should all be working now.

Rebased on main with my entry sitting below yours:

## [Fix] - 2026-09-14
- Fixed truncated memory values ...

## [Fix] - {PR_MERGE_DATE}
- App grouping now works on Windows ...

Both optional notes are done as well.

The grouped rows keep their icon now. getFileIcon needed aggregatedApp alongside app in the Windows branch, exactly as you described.

getKillGroupCommand has a fixture now:

✔ names every process in a windows group kill

It pins the generated string for one pid and for several, with and without force, so the multi /PID shape cannot drift quietly. You were right that the aggregation path itself stays uncovered on a macOS run, since the resolver only reaches the Windows key when the host is Windows. Testing the command output was the part I could pin down properly without faking the platform, so I stopped there rather than mock something and pretend it proved more than it does.

16 tests pass, ray lint is clean and ray build -e dist succeeds.

Thanks again for the patience on this, and I hope you have a good day 🙂

Comment thread extensions/kill-process/CHANGELOG.md Outdated
The placeholder becomes the merge date, which is later than the entry below it, so descending order puts it first.
@Just-Me-22

Copy link
Copy Markdown
Contributor Author

Fixed, and I want to flag that I have gone against the letter of what you asked, so please tell me if you would rather have it the other way.

You asked me to keep my entry below yours, and Greptile then flagged that a {PR_MERGE_DATE} entry has to sit at the top. I checked rather than guess between you, and the placeholder is replaced at merge time with a date later than 2026-09-14, so keeping it underneath would leave the file out of descending order once it lands. docs/basics/prepare-an-extension-for-store.md shows the placeholder directly under the title in its example, and extensions/bunq/CHANGELOG.md on main right now has exactly this shape with a pending entry above a dated one.

So it reads:

## [Fix] - {PR_MERGE_DATE}
- App grouping now works on Windows ...

## [Fix] - 2026-09-14
- Fixed truncated memory values ...

My read is that you meant keep your entry as well as mine rather than a specific order, and the ordering rule settles the rest. If you did mean the order literally then say so and I will flip it straight back, since it is your extension and one commit either way.

Nothing else changed. 16 tests, ray lint and ray build -e dist are all still green.

@pernielsentikaer pernielsentikaer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good to me, approved 🔥

@raycastbot
raycastbot merged commit d716d80 into raycast:main Sep 16, 2026
2 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Published to the Raycast Store:
https://raycast.com/rolandleth/kill-process

@raycastbot

Copy link
Copy Markdown
Collaborator

🎉 🎉 🎉

We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag.

@@ -1,5 +1,11 @@
# Kill Process Changelog

## [Fix] - 2026-09-16

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.

P2 The changelog rule requires new titles to keep {PR_MERGE_DATE} so the release job can fill in the real date. This change replaces it with 2026-09-16. Restore the placeholder.

Suggested change
## [Fix] - 2026-09-16
## [Fix] - {PR_MERGE_DATE}

Rule Used: What: Changelog entries must use {PR_MERGE_DATE} placeholder in titles, be placed at the top of the file, and maintain descending version order. Why: Standardized placeholders and consistent ordering ensure changelogs are maintainable and merge da... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/kill-process/CHANGELOG.md
Line: 3

Comment:
The changelog rule requires new titles to keep `{PR_MERGE_DATE}` so the release job can fill in the real date. This change replaces it with `2026-09-16`. Restore the placeholder.

```suggestion
## [Fix] - {PR_MERGE_DATE}
```

**Rule Used:** What: Changelog entries must use `{PR_MERGE_DATE}` placeholder in titles, be placed at the top of the file, and maintain descending version order.  Why: Standardized placeholders and consistent ordering ensure changelogs are maintainable and merge da... ([source](https://app.greptile.com/raycast/-/custom-context?memory=c2214c11-df56-490a-b1c0-09a385df481a))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@Just-Me-22
Just-Me-22 deleted the fix/kill-process-windows-grouping branch September 16, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Extension extension fix / improvement Label for PRs with extension's fix improvements extension: kill-process Issues related to the kill-process extension platform: macOS platform: Windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Kill Process] ...

3 participants