# NousResearch/hermes-agent · Desktop OS notifications and session routing fixes

## Open now

- Nothing open on GitHub.

Work on native OS notification support for the desktop app, including per-type toggles and fixes for correctly resuming or navigating to the corresponding session when a notification is clicked. Includes bug reports about session-not-found errors and window restoration on notification click.

## TL;DR

- 5 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 2 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #48999. Verify it addresses your case.
- **Recommended:** read PR #48999 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-48999,pr-107077`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.34`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #48808: \[Bug\]: Hermes Desktop on Windows — clicking notification shows "session not found"
- #65493: \[Bug\]: (Desktop)Clicking system notification should restore window and navigate to the corresponding session

## Prior & competing PRs

- **#48999: fix(desktop): resume stored session id on notification click**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `apps/desktop/src/app/desktop-controller.tsx`, `apps/desktop/src/lib/session-ids.test.ts`, `apps/desktop/src/lib/session-ids.ts`
  - fix hunks:
  - `apps/desktop/src/app/desktop-controller.tsx`

    ```diff
    @@ -14,6 +14,7 @@ import { useSkinCommand } from '@/themes/use-skin-command'
     import { formatRefValue } from '../components/assistant-ui/directive-text'
     import { getCronJobs, getSessionMessages, listAllProfileSessions, type SessionInfo, triggerCronJob } from '../hermes'
     import { type ChatMessage, chatMessageText, preserveLocalAssistantErrors, toChatMessages } from '../lib/chat-messages'
    +import { storedSessionIdForNotification } from '../lib/session-ids'
     import {
       isMessagingSource,
       LOCAL_SESSION_SOURCE_IDS,
    ```

    ```diff
    @@ -276,16 +277,20 @@ export function DesktopController() {
         }
       }, [])
     
    -  // Notification click: the main process already focused the window; jump to its session.
    +  // Notification click: the main process already focused the window; jump to its
    +  // session. Notifications are tagged with the gateway *runtime* session id, but
    +  // the chat route is keyed by the *stored* id — navigating with the runtime id
    +  // resumes a non-existent stored session ("session not found") and strands the
    +  // user. Translate runtime -> stored before navigating.
       useEffect(() => {
         const unsubscribe = window.hermesDesktop?.onFocusSession?.(sessionId => {
           if (sessionId) {
    -        navigate(sessionRoute(sessionId))
    +        navigate(sessionRoute(storedSessionIdForNotification(sessionId, runtimeIdByStoredSessionIdRef.current)))
           }
         })
     
         return () => unsubscribe?.()
    -  }, [navigate])
    +  }, [navigate, runtimeIdByStoredSessionIdRef])
     
       // Notification action button (Approve/Reject) — resolve in place, no navigation.
       useEffect(() => {
    ```
  - `apps/desktop/src/lib/session-ids.test.ts`

    ```diff
    @@ -0,0 +1,44 @@
    +import { describe, expect, it } from 'vitest'
    +
    +import { storedSessionIdForNotification } from './session-ids'
    +
    +describe('storedSessionIdForNotification', () => {
    +  it('translates a runtime id back to its stored id', () => {
    +    // The route is keyed by the stored id, but notifications carry the runtime
    +    // id. Resolving runtime -> stored keeps notification-click navigation from
    +    // resuming a non-existent stored session ("session not found").
    +    const map = new Map([['stored-abc', 'runtime-123']])
    +
    +    expect(storedSessionIdForNotification('runtime-123', map)).toBe('stored-abc')
    +  })
    +
    +  it('returns the id unchanged when no mapping is known', () => {
    +    // A notification for a session this window never opened may already carry a
    +    // stored id; let the resume/REST lookup handle it as-is.
    +    const map = new Map([['stored-abc', 'runtime-123']])
    +
    +    expect(storedSessionIdForNotification('stored-xyz', map)).toBe('stored-xyz')
    +  })
    +
    +  it('returns the id unchanged for an empty map', () => {
    +    expect(storedSessionIdForNotification('runtime-123', new Map())).toBe('runtime-123')
    +  })
    +
    +  it('resolves the correct stored id among several sessions', () => {
    +    const map = new Map([
    +      ['stored-1', 'runtime-1'],
    +      ['stored-2', 'runtime-2'],
    +      ['stored-3', 'runtime-3']
    +    ])
    +
    +    expect(storedSessionIdForNotification('runtime-2', map)).toBe('stored-2')
    +  })
    +
    +  it('does not treat a stored id as a runtime id (keys are not matched)', () => {
    +    // The map is stored -> runtime. A value that only appears as a *key* must
    +    // not be rewritten, otherwise an already-stored id could be mangled.
    +    const map = new Map([['stored-1', 'runtime-1']])
    +
    +    expect(storedSessionIdForNotification('stored-1', map)).toBe('stored-1')
    +  })
    +})
    ```
  - `apps/desktop/src/lib/session-ids.ts`

    ```diff
    @@ -0,0 +1,26 @@
    +// The gateway tags every event — and therefore every native notification —
    +// with the *runtime* session id (the key under which the session lives in the
    +// gateway's in-memory `_sessions` map). The chat route, however, is keyed by
    +// the *stored* session id (`stored_session_id`), which is a different value:
    +// a brand-new chat gets a runtime id immediately but its stored id is assigned
    +// when the first turn persists. Navigating to a runtime id therefore tries to
    +// resume a stored session that does not exist ("session not found") and
    +// strands the user, who experiences it as the running session being destroyed.
    +//
    +// `runtimeIdByStoredSessionId` maps stored -> runtime; this resolves the
    +// reverse so notification-click navigation lands on the real route. The id is
    +// returned unchanged when no mapping is known — it may already be a stored id
    +// (e.g. a notification for a session this window never opened), in which case
    +// the normal resume/REST lookup handles it.
    +export function storedSessionIdForNotification(
    +  id: string,
    +  runtimeIdByStoredSessionId: ReadonlyMap<string, string>
    +): string {
    +  for (const [storedId, runtimeId] of runtimeIdByStoredSessionId) {
    +    if (runtimeId === id) {
    +      return storedId
    +    }
    +  }
    +
    +  return id
    +}
    ```
- **#107077: fix(desktop): make native notifications reveal their session tab**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `apps/desktop/electron/main.ts`, `apps/desktop/electron/notification-actions.test.ts`, `apps/desktop/electron/notification-actions.ts`, `apps/desktop/electron/notification-ipc.test.ts`, `apps/desktop/electron/notification-ipc.ts`, `apps/desktop/electron/notification-registry.test.ts`, `apps/desktop/electron/notification-registry.ts`, `apps/desktop/electron/notification-types.ts`, `apps/desktop/src/app/contrib/hooks/notification-focus.test.tsx`, `apps/desktop/src/app/contrib/hooks/use-desktop-integrations.test.tsx`, `apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts`, `apps/desktop/src/app/open-session.test.ts`, `apps/desktop/src/app/open-session.ts`, `apps/desktop/src/global.d.ts`, `apps/desktop/src/store/native-notifications.test.ts`, `apps/desktop/src/store/native-notifications.ts`
- **#48831: fix(desktop): route notification clicks by stored session id**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#48808`, `#48999`, `#48999`

## Fix status

- Merged to main in PR #48999 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-09-10`.
- Subsystem: `apps/desktop`
