# NousResearch/hermes-agent · Desktop sidebar duplicate rows for compressed session lineages

## Open now

- Nothing open on GitHub.

One issue reports that the desktop sidebar can show duplicate rows for the same auto-compressed session lineage. Two pull requests propose deduplicating those sidebar rows, by compression lineage and by compressed session tips during merge; the titles alone do not show their status or outcome.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #43487. Verify it addresses your case.
- **Recommended:** read PR #43487 (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-43487`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: 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

- #43483: Desktop sidebar can show duplicate rows for the same auto-compressed session lineage

## Prior & competing PRs

- **#43487: fix(desktop): deduplicate sidebar rows by compression lineage**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/src/store/session.test.ts`, `apps/desktop/src/store/session.ts`
  - fix hunks:
  - `apps/desktop/src/store/session.test.ts`

    ```diff
    @@ -133,13 +133,52 @@ describe('mergeSessionPage', () => {
       it('keeps a pinned session matched by its lineage root after compression', () => {
         // The pin is stored on the lineage-root id, but the loaded row surfaces
         // under its live compression tip. Matching on _lineage_root_id keeps it.
    -    const previous = [session({ id: 'tip', _lineage_root_id: 'root' })]
    -    const incoming = [session({ id: 'other' })]
    +    const previous = [session({ id: 'tip', _lineage_root_id: 'root' })] as SessionInfo[]
    +    const incoming = [session({ id: 'other' })] as SessionInfo[]
     
         const merged = mergeSessionPage(previous, incoming, ['root'])
     
         expect(merged.map(s => s.id)).toEqual(['tip', 'other'])
       })
    +
    +  it('evicts an old compression tip when the incoming page has the new tip from the same lineage', () => {
    +    // Repro of #43483: after auto-compression rotates the tip (#4 → #5),
    +    // the sidebar showed both the old tip and the new tip as separate rows.
    +    // The old tip must be evicted because its lineage key matches the incoming
    +    // new tip's lineage key.
    +    const previous = [
    +      session({ id: 'tip-4', _lineage_root_id: 'root' }),
    +      session({ id: 'other' }),
    +    ] as SessionInfo[]
    +    const incoming = [
    +      session({ id: 'tip-5', _lineage_root_id: 'root' }),
    +    ] as SessionInfo[]
    +
    +    // 'tip-4' is in the keep set (e.g. it was the active/working session),
    +    // but should still be evicted because the incoming page carries the same
    +    // lineage under a new tip id.
    +    const merged = mergeSessionPage(previous, incoming, ['tip-4'])
    +
    +    expect(merged.map(s => s.id)).toEqual(['tip-5'])
    +    // The new tip comes from the server payload.
    +    expect(merged.find(s => s.id === 'tip-5')?._lineage_root_id).toBe('root')
    +  })
    +
    +  it('preserves an unrelated pinned session even when lineage dedup is active', () => {
    +    // Regression guard: lineage dedup must not accidentally evict sessions
    +    // from a different lineage that happen to be in the keep set.
    +    const previous = [
    +      session({ id: 'a-old', _lineage_root_id: 'lineage-a' }),
    +      session({ id: 'b', _lineage_root_id: 'lineage-b' }),
    +    ] as SessionInfo[]
    +    const incoming = [
    +      session({ id: 'a-new', _lineage_root_id: 'lineage-a' }),
    +    ] as SessionInfo[]
    +
    +    const merged = mergeSessionPage(previous, incoming, ['b'])
    +
    +    expect(merged.map(s => s.id)).toEqual(['b', 'a-new'])
    +  })
     })
     
     describe('workspaceCwdForNewSession', () => {
    ```
  - `apps/desktop/src/store/session.ts`

    ```diff
    @@ -125,10 +125,18 @@ export function mergeSessionPage(
       }
     
       const incomingIds = new Set(incoming.map(session => session.id))
    +  // Deduplicate by compression lineage: when auto-compression rotates the tip
    +  // id (old #4 → new #5), the incoming page carries the new tip but the
    +  // previous list still holds the old one.  Without lineage-level dedup both
    +  // rows survive as separate sidebar entries (fixes #43483).
    +  const incomingLineageKeys = new Set(
    +    incoming.map(session => session._lineage_root_id ?? session.id)
    +  )
     
       const survivors = previous.filter(
         session =>
           !incomingIds.has(session.id) &&
    +      !incomingLineageKeys.has(session._lineage_root_id ?? session.id) &&
           (keep.has(session.id) || (session._lineage_root_id != null && keep.has(session._lineage_root_id)))
       )
     
    ```
- **#44069: fix(desktop): dedupe compressed session tips in sidebar merge**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#43487`, `#43483`

## Fix status

- Merged to main in PR #43487 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-11`.
