Skip to content

Sidebar lists all terminated sessions indefinitely — no filter, grouping, or auto-aging #2110

@AgentWrapper

Description

@AgentWrapper

Bug

The project sidebar lists every session whose metadata file exists on disk, with no filter or grouping by terminal state. Sessions that the user has explicitly killed, sessions that auto-terminated on pr_merged, and sessions in cleanup/stuck/detecting after lifecycle resolution all stay in the sidebar indefinitely, sandwiched in with live sessions — until something deletes their JSON file (effectively only ao session cleanup, and only for sessions that meet a lifecycle-specific predicate). On a 1-2 week old machine you end up with 20+ terminated rows for every 1 live row.

Source: Live AO session, in-chat triage with Claude orchestrator | Reported by: @Itrytoohard
Analyzed against: 5897b4e8 (AgentWrapper/agent-orchestrator origin/main as of 2026-06-08)
Environment: macOS 14.6 arm64, Node v22.11.0, tmux runtime
Confidence: High — traced API + dashboard rendering, reproduced with raw /api/sessions response.

Reproduction

  1. Run AO for a week with sessions that come and go (spawn workers, merge PRs, ao session kill, get auto-terminated on merge, etc.).
  2. Open the dashboard at http://localhost:3000.
  3. Look at the left-rail project sidebar.
  4. Expected: sidebar shows live/actionable sessions; terminated sessions are either hidden, collapsed under a foldout, or auto-aged-out.
  5. Actual: every session metadata file gets rendered as a sidebar row, regardless of canonical state. Live rows mixed in with dead ones.

Concrete data from this user's machine right now (GET /api/sessions):

count: 23 sessions returned
breakdown:
  ao-1   ..  ao-5    killed/terminated     (5 rows, all manually_killed days ago)
  ao-6              killed/detecting      (1 row)
  ao-7   ..  ao-8    cleanup/terminated    (2 rows, pr_merged auto-terminated)
  ao-9              killed/detecting
  ao-10  ..  ao-14   killed/terminated     (5 rows, all from a single `ao stop` at 18:05 today)
  ao-2-1            killed/detecting
  ao-2-2            cleanup/terminated    (pr_merged from another agent days ago)
  ao-2-3 ..  ao-2-8  killed/{terminated,detecting}  (6 rows)
  ao-2-9            cleanup/terminated    (just merged its PR)
  ao-2-orchestrator working/working      (← the only LIVE row, hidden among 22 dead ones)

22 of 23 rows are not-live. The single live row (ao-2-orchestrator) is visually indistinguishable from the rest at the sidebar level.

Root Cause

The dashboard reads session listings from /api/sessions, which the running web server backs by enumerating every JSON file under ~/.agent-orchestrator/projects/<projectId>/sessions/*.json for every project in ~/.agent-orchestrator/config.yaml — independent of whether the lifecycle worker is actually polling that project. The sessions array returned has no excludeTerminal flag and the sidebar consumer (packages/web/src/components/ProjectSidebar.tsx, sourced from /api/sessions per the file comment) does not filter by lifecycle.session.state or legacy status. Terminal-state sessions are first-class items in the response.

There is no observable cleanup pressure: ao session cleanup only deletes JSON files for sessions whose lifecycle state matches a specific predicate (e.g., pr_merged with cleanup-eligible runtime state), and is opt-in. Killed sessions, stuck sessions, sessions with detecting / runtime_lost, and partially-terminated sessions all linger.

Suggested Fix

Three options sized small → large; pick the smallest the team is comfortable with:

  1. Sidebar-side filter with a toggle. In ProjectSidebar.tsx, default-hide sessions whose canonical lifecycle.session.state is terminated or done, AND whose lifecycle terminatedAt is older than e.g. 24 h. Surface a "Show terminated (N)" disclosure / toggle for the remainder so users can still reach them. No API changes. Lowest blast radius. Recommended starting point.

  2. Grouped sidebar sections. Same data, but split into "Active", "Needs attention" (stuck, needs_input, detecting), and "Recent (terminated, last 24h)" sections, with older terminated sessions collapsed under a "History" foldout. Pattern mirrors what the kanban already does for Done/Terminated per #1588.

  3. API-side scope flag. Add ?scope=active|all to /api/sessions so the sidebar can ask for the live subset and the kanban / history views can ask for everything. Reduces payload size as a bonus; today every dashboard refresh ships all 23 (or 230) session blobs.

Bonus cleanup hygiene: a periodic background trim that deletes session JSONs whose terminatedAt is older than, say, 30 days AND whose cleanup flag has fired — would prevent the on-disk pile-up that drives the sidebar bloat in the first place.

Impact

  • Sidebar is unusable as a "what's running right now" view. Once a machine accumulates more than a handful of historical sessions, the live one is needle-in-a-haystack.
  • Click confusion. Users click "terminated" rows expecting them to be openable; some are restorable, some aren't, with no consistent visual cue.
  • Cross-project clutter. Because /api/sessions aggregates across all projects in the global config — even projects the daemon isn't actively polling — terminated rows from projects you stopped weeks ago still appear (e.g. user has safesplit_3f4dcfd77a rows surfacing despite the lifecycle worker not polling that project).
  • Lifecycle-bug amplifier. Sessions that hit #19's auto-terminate-on-PR-merge loop and #1933's stuck-never-promoted-to-terminated state both linger forever in the sidebar even after the underlying issue is "resolved" by the user.

Related

  • #1875 — opposite framing: "No sessions shown" when sessions exist but ARE hidden by done/killed (suggests some filter exists somewhere, just not in the sidebar). Reconcile fix here against that one.
  • #1500 — proposed rework of session row info density / filter / grouping / active state. Same surface; this is the narrower behavior fix that ui(sidebar): rework session row — info density, filter clarity, grouping, active state #1500's UX rework would subsume.
  • #1588 — show session ID in Done/Terminated section on dashboard (so a Done/Terminated grouping exists in the kanban; sidebar lacks the same pattern).
  • #19 — auto-terminate-on-PR-merge with no opt-out. Every merged-PR session adds a fresh terminated row to this sidebar.
  • #1933stuck sessions don't auto-promote to terminated and linger. Compounds the clutter.
  • #1524ao session cleanup leaves worktrees on disk for sessions already in terminal lifecycle state. Same family of "lifecycle says dead, dashboard still shows it" problems.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions