fix(desktop): converge profile deletion — stop respawn + refresh rail (supersedes #52301, #49335) - #57329
Merged
Merged
Conversation
…vent respawn loop
When the renderer sends a DELETE /api/profiles/{name} request, the IPC
handler tears down the profile's pool backend (or primary backend) via
prepareProfileDeleteRequest. However, the very next line calls
ensureBackend(profile), which spawns a fresh pool backend for the just-
deleted profile. The new backend's startup path calls ensure_hermes_home(),
which recreates the profile directory — defeating the deletion and leaving
the process as a zombie.
On the next Desktop restart the cycle repeats: the profile directory exists,
the Desktop spawns a backend, the backend recreates the directory after
deletion, and PIDs accumulate indefinitely.
Fix: make prepareProfileDeleteRequest return the torn-down profile name.
The IPC handler uses this to route the DELETE to the primary backend
instead of spawning a new pool backend for the deleted profile.
Fixes #52279
This was referenced Jul 2, 2026
OutThisLife
enabled auto-merge (rebase)
July 2, 2026 20:17
1 task
8 tasks
teknium1
pushed a commit
that referenced
this pull request
Aug 17, 2026
Two independent bugs let a deleted profile reappear / leave orphaned resources on next launch: 1. hermes_cli/profiles.py's backend-process scanner required argv[0] to resolve to an executable literally named "hermes". Electron's pool-backend spawn resolves the hermes console-script shim's path and execs it via the interpreter directly (python3 /path/to/hermes ...), so argv[0] reports as "python3" and the scanner never matched the running backend -- delete removed the profile's files but left its live backend process running (still bound to a port via uvicorn), which accumulates across repeated delete/recreate cycles. 2. The desktop sidebar's ProfileRail only refreshed its cached profile list once, on mount, so a delete/create/rename from another surface (another window, or the CLI) left a stale ghost entry until something unrelated triggered a refetch. Note: a delete via this window's own Manage-Profiles view already refreshes the shared $profiles atom ProfileRail subscribes to (confirmed by reading refreshProfiles() and handleConfirmDelete()) -- this fix only covers the cross-window/cross- process staleness gap, not a duplicate of the already-merged #57329's Manage-Profiles rail-refresh work. Fix 1: recognize a python-interpreter argv[0] exec'ing a hermes-named console-script shim via argv[1]. Fix 2: refresh the profile list on window focus/visibilitychange, matching the existing pattern used elsewhere in the sidebar (sidebar/index.tsx, use-background-sync.ts, star-map.tsx, use-gateway-boot.ts all use the same focus+visibilitychange pattern). ## Related work already on main PR #57329 (merged) fixed the *headline* symptom from issue #52279 (deleted profile respawns) via a different, non-overlapping mechanism: routing profile-delete through the primary backend instead of spawning a fresh pool backend, plus a separate recreation guard in ensure_hermes_home() (#49435, merged) that makes a backend spawned into a deleted profile's directory raise FileNotFoundError instead of silently recreating it. This PR is NOT a duplicate of that fix. Verified: even with both of those merged, a backend process that survives because of gap #1 above still holds a bound port via uvicorn -- it just can no longer resurrect the profile directory. That's real resource-hygiene, not a symptom already covered. Gap #2 touches a different file/component (ProfileRail / profile-switcher.tsx) than #57329's rail-refresh half (which touched the Manage-Profiles view's own $profiles.ts / index.tsx) and covers a distinct staleness path (cross-window/cross-process, not same-window delete-then-refresh). Tests: tests/hermes_cli/test_profiles.py -- 156 passed (existing + regression coverage for the argv[0] python-interpreter detection case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates the two Desktop-side halves of the profile-deletion bug (#47368) into one review unit, since they compose into a single behavior — "when a profile is deleted, it actually goes away and stays gone in the UI":
prepareProfileDeleteRequestnow returns the torn-down profile name, and thehermes:apihandler routes that request to the primary backend instead of spawning a fresh pool backend. A freshly spawned pool backend callsensure_hermes_home(), which would recreate the just-deleted profile directory and leave a zombie process. This is the Desktop-side complement to the CLI recreation guard in Prevent deleted profiles from being recreated by stale dashboards #49435.refreshProfiles()helper on$profilesand points the Manage-Profiles view'srefresh()at it, so a deletion updates the shared rail cache instead of only the local list. Preserves the existing list on a failed refresh.Both commits are cherry-picked with original authorship intact.
Why one PR
These are the same language (desktop TS/Electron), the same CI lane, and the same user-visible outcome; they don't overlap in code but they finish each other's job. The CLI root-cause fix (#49435, recreation guard) lands separately — with it, a backend spawned into a deleted profile now exits on
FileNotFoundErrorinstead of resurrecting the tree, and these two changes make the Desktop stop spawning it and reflect the deletion immediately.Supersedes
Closes #52279Closes #49289Part of #47368.
Validation
tsc -p . --noEmit(apps/desktop)node --test electron/profile-delete-respawn.test.cjsvitest run src/store/profile.test.tseslint(all touched files)#49335 was stale against
main(import drift around$profileColors/getProfileSetupCommandand the starmap-cache test block); reconciled during the cherry-pick.Co-authored-by: liuhao1024 sunsky.lau@gmail.com
Co-authored-by: Tranquil-Flow 66773372+Tranquil-Flow@users.noreply.github.com