Skip to content

bug(desktop): new-session composer prefilled with another session's first input (draft cross-session leak) #63054

Description

@Cozieur

Problem

When opening a new chat session in Hermes Desktop, the composer is pre‑filled with text that was the first input of a different (previous) session — not an empty box, and not the user's own __new__ draft. The user must manually clear it before typing.

This is not the intended per‑session draft‑restore behavior. A brand‑new session should start empty (or with the user's own __new__ bucket, which should be empty).

Environment

  • Hermes.app: 0.17.0 (install‑stamp builtAt: 2026-07-12T07:05:57Z, commit: 2043436af, source: local)
  • CLI: 0.18.2
  • macOS (host reports 26.6)
  • Reproduced with Chinese IME, but the leak is language‑independent.

Suspected root cause (from source inspection)

Draft storage is a single localStorage map keyed by session scope:

  • apps/desktop/src/store/composer.ts:27SESSION_DRAFTS_STORAGE_KEY = 'hermes:composer-drafts:v3'
  • apps/desktop/src/store/composer.ts:29NEW_SESSION_DRAFT_KEY = '__new__'
  • apps/desktop/src/store/composer.ts:38draftKey(scope) falls back to __new__ when scope is empty/whitespace; keys otherwise by activeQueueSessionKey.

The per‑thread swap lives in useComposerDraft:

  • apps/desktop/src/app/chat/composer/hooks/use-composer-draft.ts:309-331 — on activeQueueSessionKey change it does takeSessionDraft(activeQueueSessionKey)loadIntoComposer(text, attachments).
  • apps/desktop/src/app/chat/composer/hooks/use-composer-draft.ts:222-262 — the composerRuntime.subscribe(sync) debounced stash (DRAFT_PERSIST_DEBOUNCE_MS) writes draftRef.current under draftScopeRef.current.
  • apps/desktop/src/app/chat/composer/hooks/use-composer-draft.ts:90-91, 237draftScopeRef is meant to track "the session whose text is actually loaded in the editor" to avoid a session‑switch‑mid‑flight writing one session's draft under another's key (Bug: Gateway message routing misdirects user input between TUI sessions -- prompt permanently lost #54527).

The cross‑session prefill indicates the scope/key used at stash or restore time does not match the actually‑visible session in some transition (new‑session creation, rapid session switch, or a thread swap where activeQueueSessionKey is briefly null/empty and resolves to __new__ but the restored text came from a real session id). Worth checking the takeSessionDraft key path against the key used when the outgoing session was stashed, especially around the __new__ bucket.

Workaround (verified)

Desktop Cmd+Option+I → Console:

localStorage.removeItem('hermes:composer-drafts:v3')

then restart Hermes.app. The prefill disappears (clears cached drafts only; sent messages untouched). The leak recurs until the root cause is fixed.

Suggested fix direction

Audit the session‑key passed to stashSessionDraft / takeSessionDraft across the new‑session transition and rapid switches; ensure a freshly created session never restores a real session id's draft, and that the __new__ bucket is only ever written with the user's in‑progress (unsent) new‑chat text.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havearea/sessionsSession lifecycle, resume, persistence, historycomp/desktopElectron desktop app (apps/desktop/*)sweeper:risk-session-stateSweeper risk: may lose/corrupt/mis-associate session or context statetype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions