Skip to content

fix(web): hide sidebar fake sessions for Cursor resume/archive#836

Merged
tiann merged 4 commits into
tiann:mainfrom
swear01:fix/cursor-session-sidebar-dedup-833
Jun 8, 2026
Merged

fix(web): hide sidebar fake sessions for Cursor resume/archive#836
tiann merged 4 commits into
tiann:mainfrom
swear01:fix/cursor-session-sidebar-dedup-833

Conversation

@swear01
Copy link
Copy Markdown
Contributor

@swear01 swear01 commented Jun 7, 2026

Summary

  • Wire prepareSidebarSessions into SessionList: dedupe by metadata.agentSessionId, then hide inactive empty stubs (no agent id, no name/summary)
  • Always keep active and currently selected sessions visible
  • Expose lifecycleState on SessionSummary for future sidebar rules
  • Use filtered session count in sidebar header

Fixes #833

Test plan

  • SessionList.test.ts — dedup, stub filter, selected-stub visibility
  • sessionSummary.test.ts — lifecycleState mapping
  • bun typecheck
  • bun run test:web / bun run test:shared
  • Live (port 43006): spawn → archive → no ghost sidebar row; resume → one row per thread

swear01 and others added 2 commits June 7, 2026 12:11
Wire deduplicateSessionsByAgentId into SessionList and resolve cursor
threads via cursorSessionId so resume/archive no longer shows duplicate
inactive rows for the same ACP session.

Fixes tiann#833

Co-authored-by: Cursor <cursoragent@cursor.com>
SessionList only receives SessionSummary from the API; native ids like
cursorSessionId are already mapped into metadata.agentSessionId by
toSessionSummary. Drop resolveAgentSessionIdFromMetadata to fix typecheck.

Co-authored-by: Cursor <cursoragent@cursor.com>
@swear01
Copy link
Copy Markdown
Contributor Author

swear01 commented Jun 7, 2026

Scope correction + CI fix

Pushed a follow-up commit to fix typecheck and clarify what this PR actually fixes.

What was wrong in the first commit

  • Called resolveAgentSessionIdFromMetadata() on SessionSummary.metadata, but sidebar summaries only expose metadata.agentSessionId (native ids like cursorSessionId are already mapped in shared/src/sessionSummary.tstoSessionSummary).
  • That caused CI tsc failures and did not add real behavior beyond using agentSessionId.

What this PR does now

  • Wires deduplicateSessionsByAgentId(props.sessions, selectedSessionId) into SessionList rendering.
  • Dedup key: metadata.agentSessionId (includes Cursor via summary mapping).

What this PR does not fix (#833 follow-up)

Live repro on test hub shows the main clutter is:

  1. Archived rows still listed (no sidebar filter)
  2. 0-message stubs without agentSessionId (spawn → quick archive) — dedup intentionally passes these through

See comment on #833 for Repro A/B details. Recommend follow-up PR for hiding archived stubs / empty inactive rows.

Related: #834 helps resume metadata timing; not sidebar volume.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Dedupe can hide unrelated cross-flavor sessions — SessionSummary.metadata.agentSessionId is a flattened field, and toSessionSummary fills it by the first available native id (codexSessionId ?? claudeSessionId ?? ... ?? cursorSessionId) rather than the current flavor-specific id. Now that SessionList always renders through deduplicateSessionsByAgentId, a Cursor session carrying a stale codexSessionId can collide with a real Codex session and disappear from the sidebar, even though hub resume/dedupe logic treats those fields as flavor-specific (resolveAgentResumeId, extractAgentSessionId). Evidence: web/src/components/SessionList.tsx:745, related context shared/src/sessionSummary.ts:65 and web/src/lib/sessionResume.ts:13.
    Suggested fix:
    function getSessionDedupKey(session: SessionSummary): string | null {
        const agentId = session.metadata?.agentSessionId
        if (!agentId) return null
        return `${session.metadata?.flavor ?? 'unknown'}:${agentId}`
    }

Summary

  • Review mode: initial
  • One Major issue found: the new rendered dedupe path should scope the flattened resume id by flavor, or the upstream summary should expose only the current flavor's native id before web-side dedupe uses it.

Testing

  • Not run locally: bun is not installed in this runner (/bin/bash: bun: command not found). Add/adjust a SessionList.test.ts case with a Codex row and a Cursor row sharing the same flattened/stale id to cover this regression.

HAPI Bot

Comment thread web/src/components/SessionList.tsx Outdated
const allSessions = useMemo(
() => props.sessions,
[props.sessions]
() => deduplicateSessionsByAgentId(props.sessions, selectedSessionId),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] This now runs every rendered session through deduplicateSessionsByAgentId, but the helper keys only on flattened metadata.agentSessionId. toSessionSummary populates that field by first available native id (codexSessionId ?? claudeSessionId ?? ... ?? cursorSessionId), while resume logic is flavor-specific and already ignores stale cross-flavor ids. A Cursor row with a stale codexSessionId can therefore collide with and hide a real Codex row in the sidebar.

Suggested fix:

function getSessionDedupKey(session: SessionSummary): string | null {
    const agentId = session.metadata?.agentSessionId
    if (!agentId) return null
    return `${session.metadata?.flavor ?? 'unknown'}:${agentId}`
}

Filter inactive rows with no agentSessionId and no title signal before
grouping sessions, and expose lifecycleState on SessionSummary for future
sidebar rules. Completes the tiann#833 P0 follow-up alongside agent-id dedup.

Fixes tiann#833

Co-authored-by: Cursor <cursoragent@cursor.com>
@swear01 swear01 changed the title fix(web): dedupe sidebar sessions by flavor resume id fix(web): hide sidebar fake sessions for Cursor resume/archive Jun 7, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Sidebar dedupe can still hide unrelated cross-flavor sessions — the rendered list now flows through prepareSidebarSessions, which still calls deduplicateSessionsByAgentId using only the flattened metadata.agentSessionId. toSessionSummary fills that value from the first native id present (codexSessionId ?? claudeSessionId ?? ... ?? cursorSessionId), but the hub/web resume paths resolve ids by current flavor, so a Cursor row that preserved a stale Codex id can collide with a real Codex row and disappear from the sidebar. Evidence: web/src/components/SessionList.tsx:773, related context shared/src/sessionSummary.ts:66 and web/src/lib/sessionResume.ts:13.
    Suggested fix:
    function getSessionDedupKey(session: SessionSummary): string | null {
        const agentId = session.metadata?.agentSessionId?.trim()
        if (!agentId) return null
        return `${session.metadata?.flavor ?? 'unknown'}:${agentId}`
    }

Summary

  • Review mode: follow-up after new commits
  • One unresolved Major issue remains from the full PR: sidebar dedupe should not use the unscoped flattened agent id as a global key.

Testing

  • Not run (automation): bun is unavailable in this runner (/bin/bash: bun: command not found). Add a SessionList.test.ts case with a Codex summary and a Cursor summary sharing the same stale flattened id to lock this down.

HAPI Bot

const allSessions = useMemo(
() => props.sessions,
[props.sessions]
() => prepareSidebarSessions(props.sessions, selectedSessionId),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] prepareSidebarSessions still sends the rendered sidebar through deduplicateSessionsByAgentId, whose key is only metadata.agentSessionId. That field is flattened in toSessionSummary by first available native id, while resume logic is flavor-specific. If a Cursor session preserves a stale codexSessionId, it can collide with a real Codex session and be dropped from the sidebar.

Suggested fix:

function getSessionDedupKey(session: SessionSummary): string | null {
    const agentId = session.metadata?.agentSessionId?.trim()
    if (!agentId) return null
    return `${session.metadata?.flavor ?? 'unknown'}:${agentId}`
}

Prevent cross-flavor collisions when flattened agentSessionId retains a
stale native id. Add regression test and relax claudeRemote CI timeout.

Fixes tiann#833

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • No issues found.

Summary

  • Review mode: follow-up after new commits
  • Full diff reviewed. The previous Major around cross-flavor agentSessionId dedupe is addressed by scoping the dedupe key by flavor and adding a regression case.
  • Residual risk: I could not independently run the test suite in this runner.

Testing

  • Not run (automation): bun is unavailable in this runner (/bin/bash: bun: command not found).

HAPI Bot

heavygee added a commit to heavygee/hapi that referenced this pull request Jun 7, 2026
…nn#835

Revert agentCliGuard.ts + AcpStdioTransport.ts + agentCliGuard.test.ts to
upstream/main. Strip the agent-acp-active lock pre-check from
maybeAutoMigrateLegacyCursorSession + simplify its test suite to match.

Per tiann#832 (filed by swear01), the assumption underpinning my
v8 strict-mkdirSync refusal — that Cursor's `agent` binary SIGTERMs a
second `agent acp` process on the same host — is incorrect. Two
`agent acp` processes coexist fine. The lock's actual job is narrower:
prevent `agent --list-models` from killing an active `agent acp` child.

swear01's open PR tiann#835 ("fix(cursor): merge SKU catalog under ACP lock
and refcount agent guard") refactors agent-acp-active from a
single-PID file to a cross-process refcount with live-pid
reconciliation. We expect tiann#835 to land before tiann#824 (this PR) and have
added swear01's three open ACP mop-up PRs (tiann#835/tiann#836/tiann#837) to the
local driver-manifest soup so the migrator is exercised against the
eventual upstream shape.

Net effect on tiann#824:
  * agent-acp-active lock concerns are someone else's problem now
    (the refcount lock in tiann#835 just works for our migrator flow).
  * Verify probe still runs in isolated HAPI_HOME (unchanged) — that
    isolation is independent of the host-side lock semantics and
    remains the correct safety boundary for the probe.
  * Auto-migrate trigger in resumeSession is unchanged behaviourally;
    only the now-obsolete lock pre-check is gone.

53/53 unit tests pass (cursorLegacyMigrator + syncEngineAutoMigrate).

Co-authored-by: Cursor <cursoragent@cursor.com>
@tiann tiann merged commit 8094b50 into tiann:main Jun 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(web,cli): Cursor resume/archive leaves orphan sessions visible in sidebar

2 participants