Skip to content

Sidebar dot + kanban badge locked to PR status — agent working/idle state invisible once PR exists #2944

Description

@i-trytoohard

Bug

Once a session has an open PR, its sidebar dot and kanban badge are locked to the PR lifecycle color (blue/"pending") regardless of whether the agent is actively working or idle. Users lose the ability to see real-time activity state from the sidebar/kanban — the dot never shows "working" (orange) again.

Source: Discord — #🧑‍🔧-bug-triage | Reported by: @prateek | Analyzed against: cabf6610 (main, 2026-07-21)
Confidence: High

Screenshot

The gtm-strategy session is actively Working (orange pill in topbar, "Working — just now" in activity feed), but its sidebar dot is blue — indistinguishable from an idle session with an open PR.

screenshot

Reproduction

  1. Spawn a worker session and let it open a PR (status derives to pr_open or review_pending).
  2. While the PR is open, send the agent another task or have it continue working (activity state → active).
  3. Observe the sidebar dot: it stays blue (pending zone) even though the agent is actively working.
  4. Compare with the topbar, which correctly shows orange "Working" (from session.activity.state, decoupled in PR fix: sync topbar activity pill #2541).
  5. Same behavior on the kanban board — the card badge reflects PR status, not activity.

Root Cause

backend/internal/service/session/status.go, deriveStatus() checks for open PRs before checking agent activity:

// line 35-37: needs_input wins (correct)
if rec.Activity.State.NeedsInput() {
    return domain.StatusNeedsInput
}

// line 39-42: open-PR check runs FIRST — short-circuits activity
open := openPRs(prs)
if len(open) > 0 {
    return aggregatePRStatus(open)   // ← returns review_pending/pr_open/etc.
}

// line 47-49: activity check only reached if NO open PR
if rec.Activity.State == domain.ActivityActive {
    return domain.StatusWorking      // ← unreachable when a PR exists
}

Once openPRs() returns non-empty, deriveStatus() returns a PR-based status and never evaluates ActivityActive. So pr_open + active and pr_open + idle produce the same status — the sidebar dot and kanban badge are identical in both cases.

The frontend consumes this in two places, both reading only session.status (never activity.state):

  • Sidebar (Sidebar.tsx:125-127): getSessionDotView(session)getAttentionZoneView(session.status)attentionZone() → blue "pending" dot for any PR-derived status
  • Kanban (SessionsBoard.tsx:92, 467): column placement via attentionZone(session) + badge via getSessionStatusView(session.status)

The topbar was already fixed to read session.activity.state directly (PR #2541), so it correctly shows "Working" — but the sidebar and kanban were not decoupled, creating the disagreement visible in the screenshot.

Note: this is the inverse of #2345

#2345 describes the opposite symptom: the topbar showing "working" for idle+PR sessions. Both bugs stem from the same architectural tension — status and activity are derived independently, and different surfaces pick different signals — but the root causes are in different layers:

Fix

Move the ActivityActive check before the PR aggregation — same precedence that NeedsInput() already enjoys (line 35). In deriveStatus():

if rec.Activity.State.NeedsInput() {
    return domain.StatusNeedsInput
}
if rec.Activity.State == domain.ActivityActive {   // ← moved up
    return domain.StatusWorking
}

open := openPRs(prs)
if len(open) > 0 {
    return aggregatePRStatus(open)
}

Result: agent actively working → orange dot / "Working" column, regardless of PR. Agent goes idle → status falls back to PR lifecycle (blue review_pending, red ci_failed, green approved). The PR lifecycle remains visible in the right panel and board card PR groups — no information loss, just no longer competing with the real-time working signal.

Consistent with existing design: needs_input already takes precedence over PR status for the same reason — it's the signal that most needs the user's attention right now.

Impact

  • Severity: Medium — no data loss, but the primary navigation surfaces (sidebar, kanban) lose a real-time signal users rely on to know which agents are actively working. This defeats the purpose of the multi-session dashboard.
  • Scope: Every session that has an open PR and is still doing work (addressing reviews, fixing CI, iterating). With Claude Code's tool-use hooks keeping activity accurate, this is a large fraction of active sessions.
  • Affected surfaces: Sidebar session dots, kanban board badge colors and column placement.

Related

  • #2345 — inverse symptom (topbar shows "working" for idle+PR); same architectural tension, different layer
  • #2728 — UI/UX Audit, AO-10 (P0): "Completion state is inconsistent across terminal, board, and session"
  • #1985 — feat: simplify sidebar dot colors to 5-bucket scheme centered on human action
  • PR fix: sync topbar activity pill #2541 — topbar decoupled to read session.activity.state directly (the half of this fix that already landed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions