You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds an explicit Make primary Desktop profile action to the existing Manage Profiles row menu. This uses the existing hermes:profile:set lifecycle to persist the selection, restart the primary backend under the selected HERMES_HOME, and reload the window.
This action is intentionally separate from ordinary workspace/gateway navigation. selectProfile() continues to switch the live chat/session context without changing the persisted Desktop primary profile.
Electron already exposed profile.set(), and the renderer retained switchProfile(), but the Profiles view no longer had a UI action wired to that path. Once active-profile.json contained a named profile, users could navigate the default workspace but could not explicitly restore default as the persisted primary Desktop profile.
Type of Change
🐛 Bug fix (non-breaking change that fixes an issue)
✨ New feature (non-breaking change that adds functionality)
🔒 Security fix
📝 Documentation update
✅ Tests (adding or improving test coverage)
♻️ Refactor (no behavior change)
🎯 New skill (bundled or hub)
Changes Made
Added the explicit primary-profile action and a Primary for Desktop badge in apps/desktop/src/app/profiles/index.tsx, with localized copy in all Desktop locales.
Kept confirmed running-primary state separate from the persisted preference in apps/desktop/src/store/profile.ts, so no-op suppression does not block recovery after a partial restart failure.
Factored the existing active-profile.json write → primary teardown → window reload lifecycle into apps/desktop/electron/desktop-primary-profile.ts for direct regression coverage.
Extended the existing renderer/profile tests and added focused Electron lifecycle tests.
How to Test
Commands run from apps/desktop:
npx vitest run --project ui src/store/profile.test.ts src/app/profiles/index.test.tsx
npx vitest run --project electron electron/desktop-primary-profile.test.ts
npm run check
Results:
Focused renderer/store tests: 25 passed.
Focused Electron lifecycle tests: 4 passed.
Full npm run check: typecheck passed; lint completed with 0 errors and 88 existing warnings; 3,853 renderer tests passed; 1,059 Electron tests passed with 2 skipped; macOS arm64 app/DMG packaging passed.
git diff --check: passed.
Manual reproduction:
Create a named profile such as dev, make it the primary Desktop profile, and confirm Desktop reloads with active-profile.json containing {"profile":"dev"}.
Open Manage Profiles, open the row menu for default, and choose Make primary Desktop profile.
Confirm Desktop reloads under the default/root HERMES_HOME and active-profile.json now contains {"profile":"default"}.
Navigate between profile workspaces from the profile rail and confirm those clicks do not rewrite active-profile.json or restart the primary backend.
Open the current primary profile's row menu and confirm the primary action is disabled.
Regression Safety
Ordinary selectProfile() / gateway workspace switching is unchanged and never calls profile.set(). The change introduces no new config key or migration and does not alter profile credentials, configuration, sessions, or isolation. The Electron path continues to use the existing platform-native app.getPath('userData') location and atomic active-profile.json replacement before primary-backend teardown and reload.
My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
I searched for existing PRs to make sure this isn't a duplicate
My PR contains only changes related to this fix/feature (no unrelated commits)
I've run pytest tests/ -q and all tests pass
I've added tests for my changes (required for bug fixes, strongly encouraged for features)
I've tested on my platform: macOS 26.5.2, arm64
Documentation & Housekeeping
I've updated relevant documentation (README, docs/, docstrings) — or N/A
I've updated cli-config.yaml.example if I added/changed config keys — or N/A
I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
I've updated tool descriptions/schemas if I changed tool behavior — or N/A
Screenshots / Logs
Not included. The new action reuses the existing Profiles row-menu pattern; focused UI tests cover its enabled, disabled, default, and named-profile states.
AI code review — automated review for reference, author can ignore or act on any point.
fix(desktop): allow switching primary profile back to default
apps/desktop/electron/desktop-primary-profile.ts:writeDesktopPrimaryProfile — the new implementation (writeFileSync to a .tmp + renameSync) replaces the previous writeFileAtomic helper. It drops the fsync that made the old write durable, and a failed renameSync (e.g. an AV lock on Windows) leaves a stale .tmp behind. Consider restoring the atomic-write utility (fsync + temp cleanup) so the persist step is at least as robust as the code it replaces.
apps/desktop/src/store/profile.ts:switchProfile — the failure-restore path compares atom state by object identity ($primaryDesktopProfileState.get() === switchingState). If any concurrent store update happens between the two awaited profile.get() calls, the identity check fails and the restore is skipped, leaving a stale { current: null, persisted: target }. Comparing by value (or snapshotting the previous state fields up front) would be more robust against interleaved updates.
applyDesktopPrimaryProfile persists the new profile before tearing down the backend. If teardown fails, the persisted file and the running backend disagree until the retry completes — the retry flow is well tested, but the UI pill can briefly reflect the new profile while the old backend is still running. A status hint when the teardown error surfaces would make that state visible.
Extracting the persistence logic into desktop-primary-profile.ts with deterministic unit tests (including the failed-teardown retry) is a solid refactor, and all locale files were updated with the new keys. No issues there.
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
area/profilesMulti-profile isolation, HERMES_HOME scopingcomp/desktopElectron desktop app (apps/desktop/*)P2Medium — degraded but workaround existssweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradestype/bugSomething isn't working
3 participants
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.
What does this PR do?
Adds an explicit Make primary Desktop profile action to the existing Manage Profiles row menu. This uses the existing
hermes:profile:setlifecycle to persist the selection, restart the primary backend under the selectedHERMES_HOME, and reload the window.This action is intentionally separate from ordinary workspace/gateway navigation.
selectProfile()continues to switch the live chat/session context without changing the persisted Desktop primary profile.Related Issue
Fixes #85991
Root Cause
Electron already exposed
profile.set(), and the renderer retainedswitchProfile(), but the Profiles view no longer had a UI action wired to that path. Onceactive-profile.jsoncontained a named profile, users could navigate the default workspace but could not explicitly restoredefaultas the persisted primary Desktop profile.Type of Change
Changes Made
apps/desktop/src/app/profiles/index.tsx, with localized copy in all Desktop locales.apps/desktop/src/store/profile.ts, so no-op suppression does not block recovery after a partial restart failure.active-profile.jsonwrite → primary teardown → window reload lifecycle intoapps/desktop/electron/desktop-primary-profile.tsfor direct regression coverage.How to Test
Commands run from
apps/desktop:Results:
npm run check: typecheck passed; lint completed with 0 errors and 88 existing warnings; 3,853 renderer tests passed; 1,059 Electron tests passed with 2 skipped; macOS arm64 app/DMG packaging passed.git diff --check: passed.Manual reproduction:
dev, make it the primary Desktop profile, and confirm Desktop reloads withactive-profile.jsoncontaining{"profile":"dev"}.HERMES_HOMEandactive-profile.jsonnow contains{"profile":"default"}.active-profile.jsonor restart the primary backend.Regression Safety
Ordinary
selectProfile()/ gateway workspace switching is unchanged and never callsprofile.set(). The change introduces no new config key or migration and does not alter profile credentials, configuration, sessions, or isolation. The Electron path continues to use the existing platform-nativeapp.getPath('userData')location and atomicactive-profile.jsonreplacement before primary-backend teardown and reload.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Not included. The new action reuses the existing Profiles row-menu pattern; focused UI tests cover its enabled, disabled, default, and named-profile states.