[BUG] Desktop activity dashboard streak/heatmap credits the session-start date, not each active calendar day - multi-day sessions wrongly break the streak

Status Fixed / completed
Reported on v2.1.114
Maintainer reply None cached
Activity 10 comments · opened Jun 10, 2026 · closed Aug 25, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The activity dashboard in the Claude Code Desktop app (the "What's up next" welcome screen with Sessions / Messages / Active days / Current streak / Longest streak / heatmap) appears to attribute a session's activity to a single date rather than to every calendar day on which messages were actually sent.

Hypothesis: the streak and heatmap credit the day a session STARTS, not each day the session is active. A single long-lived session that spans multiple calendar days therefore lands entirely on its start date, leaving the intervening days blank and breaking the streak - even though messages were genuinely sent on those days.

This disproportionately affects users who run long continuous sessions, which the worktree-isolated desktop workflow actively encourages.

Evidence

  • I inadvertently ran one continuous session from Jun 9 into the early hours of Jun 11.
  • That session contains a message timestamped Jun 10, 2026, 9:57 PM (local) - clear activity on the 10th.
  • Despite that, the 10th is not credited as an active day, and my Current streak shows 0d. My Longest streak (30d) was effectively ended by a day I was demonstrably active.

Expected Behavior

Each calendar day on which at least one message is sent should count as an active day for the heatmap and streak, regardless of which day the containing session was started.

Actual Behavior

Days on which activity occurred within an already-running session are not counted. The streak breaks despite genuine daily activity.

Possible Secondary Factor

Worth checking whether day-bucketing uses UTC vs the user's local timezone. I'm in New Zealand (UTC+12), so late-evening local activity falls near or across a UTC date boundary, which could compound the mis-bucketing.

Environment

  • Product: Claude Code Desktop app (macOS)
  • Hardware: Apple Silicon (M2 Max)
  • App version: Claude Desktop 1.11847.5
  • Timezone: Pacific/Auckland (UTC+12)

What Should Happen?

Each calendar day on which at least one message is sent should count as an active day for the heatmap and streak, regardless of which day the containing session was started.

Error Messages/Logs

N/A - no error output. This is a silent miscount in the activity dashboard display, not a crash.

Steps to Reproduce

  1. Start a session shortly before midnight (local time).
  1. Keep working in that same session past midnight, into the next calendar day (and ideally a third day).
  1. Open the activity dashboard.
  1. Observe that only the session's start date is credited in the heatmap; the later day(s) show as inactive and the current streak resets.

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

N/A

Claude Code Version

Claude Code 2.1.114

Platform

Other

Operating System

macOS

Terminal/Shell

Other

Additional Information

Auth/platform: Claude Pro subscription via the Claude Code Desktop app (not an Anthropic API key, Bedrock, or Vertex).

Reproduced in the Claude Code Desktop app (Code tab) GUI, not a terminal session.

Worth noting the user-impact angle: a streak counter punishing you for a technicality would likely do the opposite of what gamification is for.

<img width="627" height="463" alt="Image" src="https://github.com/user-attachments/assets/56e9d4a7-594e-4bff-bc91-3a0bbed2387e" />

<img width="879" height="346" alt="Image" src="https://github.com/user-attachments/assets/9d76b1f9-9ad6-4b3d-85af-fe50ee88f5b1" />

<img width="575" height="252" alt="Image" src="https://github.com/user-attachments/assets/727e833e-90e6-40ca-a262-e29d7a0234e9" />

<img width="538" height="272" alt="Image" src="https://github.com/user-attachments/assets/d7af32c5-241c-4a7e-a45f-bdf77f2ecac3" />

<img width="570" height="250" alt="Image" src="https://github.com/user-attachments/assets/62ce0816-337f-445c-a96c-46c7661635a2" />

<img width="467" height="222" alt="Image" src="https://github.com/user-attachments/assets/e32dbc45-6db5-4bfa-8946-26b81e62fc9c" />

View original on GitHub ↗

9 Comments

sohan1611 · 2 months ago

Confirmed on Windows 11, v2.1.177 — adding a workaround that works today.

Root cause (verified): The stats panel reads the first user-event timestamp per JSONL file for heatmap/active-days/streak attribution — not individual message timestamps within a continuing session. A session started on Jun 13 and continuing through Jun 14–18 lives in a single JSONL file, so all activity registers as one Jun 13 session.

Evidence from my files:

| File start date | Lines |
|---|---|
| Jun 13 | 986 (session continued for 5 days) |
| Jun 14 | 1,400 |
| Jun 15 | 3,228 |
| Jun 17 | 1,479 |
| Jun 18 | 41 |

Before fix: Sessions: 2, Active days: 1, Streak: 1d. After creating one stub JSONL per missing day: Sessions: 6, Active days: 5, Streak: 2d. Root cause confirmed.

Workaround — create one minimal stub JSONL per missing day, then restart Claude Code:

import json, uuid
from pathlib import Path
from datetime import datetime, timezone

missing_days = [
    datetime(2026, 6, 14, 10, 0, 0, tzinfo=timezone.utc),
    datetime(2026, 6, 15, 10, 0, 0, tzinfo=timezone.utc),
    datetime(2026, 6, 17, 10, 0, 0, tzinfo=timezone.utc),
]

# Replace YOUR_PROJECT_SLUG with the folder name under ~/.claude/projects/
sessions_dir = Path.home() / ".claude" / "projects" / "YOUR_PROJECT_SLUG"

for dt in missing_days:
    stub = {"type": "user", "message": {"role": "user", "content": "stub"}, "timestamp": dt.isoformat()}
    path = sessions_dir / f"{uuid.uuid4()}.jsonl"
    path.write_text(json.dumps(stub) + "\n")
    print(f"Created stub for {dt.date()}: {path.name}")

print("Restart Claude Code to update stats.")

Restart the app after running — stats update on startup.

---
Reported by: @sohan1611

NvMayMay · 2 months ago

Confirming this on the CLI (/usage → Stats), v2.1.170, WSL2 / Ubuntu 24.04.

The bug is demonstrable from /usage's own numbers without any external tooling. It reports two things that can't both be true:

Longest session:  5d 10h 25m     ← a single session spanning 5+ days
Current streak:   1 day
Active days:      16 / 734
Longest streak:   9 days

A 5-day session alongside a 1-day current streak only makes sense if a day is credited to the session's start date instead of to each calendar day its messages land on - exactly as described here.

Ground truth from the in-content timestamps over the same window: 34 consecutive active days (2026-05-19 → 2026-06-21), zero gaps. /usage shows 16 active and a 1-day streak.

Anyone can reproduce the true count:

find ~/.claude/projects -name '*.jsonl' \
  -exec grep -ho '"timestamp":"[0-9-]\{10\}' {} + \
  | sed 's/.*"//' | sort -u | wc -l

(Likely compounded by UTC vs local-tz day-bucketing, as https://github.com/anthropics/claude-code/issues/67085 notes.) +1 - the fix is to bucket active-days/streak/heatmap by per-message timestamp, not session start date.

ymaxymovych · 1 month ago

Confirming this on Windows desktop (new-session stats card) — same root cause, with concrete numbers in case a fresh repro helps prioritize.

Symptom: the heatmap showed a recent calendar day as empty (0), breaking what the card reported as a 55-day "longest" / 2-day "current" streak.

Reality, measured directly from ~/.claude/projects/**/*.jsonl:

  • That "empty" day actually had 4,913 events (messages + tool calls) — a normal, fairly heavy working day, more than the two days around it.
  • Counting each message by its own timestamp in local time, the real current streak is 80 consecutive days (no gaps), yet the card shows Longest 55 / Current 2.

Mechanism (two compounding bugs):

  1. Session-start-date bucketing. A long session that was opened weeks earlier (and kept resumed) absorbs the credit for every subsequent calendar day's activity onto its creation date. All work done inside that session on later days lands on the start day, so those later day-cells read 0. This matches #65775, #65611 and the desktop report above.
  2. UTC vs local-time bucketing. Transcripts store timestamps in UTC. Work done after local midnight (here UTC+3) is attributed to the previous calendar day, which further hollows out day-cells and can anchor the streak to an old data gap (cf. #64725).

Suggested fix: compute active days, current/longest streak, heatmap intensity, and peak hour by bucketing each message's timestamp in the user's local timezone, not by session-creation date.

Why it matters (user impact): for people who use Claude Code every day, this card is a meaningful motivation signal — a visible record of an unbroken daily habit. Silently zeroing out real working days and resetting a months-long streak is discouraging and, more importantly, wrong — the underlying data is intact; only the aggregation is off. Would love to see this fixed. Happy to provide an anonymized per-day event count if useful.

SaadK29 · 1 month ago

Confirming on Windows 11, Claude Code desktop app — managed core 2.1.197 (Jun 30 build), MSIX shell 1.18286.0 (Jul 4, 2026).

I reproduced the dashboard's numbers exactly from the local transcripts (~/.claude/projects/**/*.jsonl): counting non-sidechain sessions and their user+assistant events matched the dashboard to the message (91 sessions / 40,002 messages at first check; 92 / 40,036 after one more session was created). Grouping each session's lifetime messages under its session-start date reproduces every heatmap cell value, confirming the attribution this issue describes.

Concrete failure: July 3 had 101 real messages on disk, but all of them were in two sessions opened July 2, so the July 3 bucket is 0, the heatmap shows a gap, and the streak reset to 1d despite 12+ consecutive active days of actual usage.

The one-day label offset tracked in #65665 / #67625 is also still present in this version and compounds the confusion: the bucket keyed 2026-07-02 (733 msgs) shows the tooltip "Jul 1 — 733", and the empty 2026-07-03 bucket shows "Jul 2 — 0" (system TZ UTC-4). So the visible gap appears under a label one day earlier than the actual empty bucket.

Expected: bin individual message timestamps by local calendar day, and match tooltip/label dates to bucket dates.

Edit: corrected the version line — I originally wrote "desktop 2.1.160", but that was a stale npm CLI on my PATH. The dashboard actually runs the app-managed core 2.1.197, so both behaviors reproduce on a near-current build.

wenmco · 1 month ago

Possibly a second, distinct root cause for the same symptom — worth checking alongside the session-start-date hypothesis.

On the same desktop dashboard, my blank-days / Current streak 0d turned out to be a UTC-vs-local timezone bucketing issue, not session-start attribution. I ruled out the session-start theory for my data by bucketing on per-message timestamps directly and still seeing the shift.

Environment

  • Claude Code desktop app, Claude 1.18286.0 (259c3f) 2026-07-02T07:11:03.000Z
  • macOS, system timezone America/New_York (UTC−4 during DST)

Evidence
The session JSONL "timestamp" fields are stored in UTC. When I bucket them by UTC date vs my local (EDT) date, evening sessions jump a day:

  • A day I worked heavily (~1,150 messages, ~2–8 PM local) shows correctly on that date.
  • Sessions run ~8 PM local are stamped 00:00Z the next day, so they land on the following UTC cell — and the real local day reads as 0.
  • Concretely: the cell for "the next day" showed a small count while that day had zero local sessions; converting every timestamp to America/New_York moved those events back onto the prior day and made the counts match what I actually did.
  • Recomputing active-days by local date also recovered several days that UTC bucketing had merged into adjacent days.

So on a negative-UTC-offset timezone this manifests as the classic "recent dates off by one day" — the tooltip/heatmap credits the UTC day, not the local day.

Likely same underlying cause as #65665 (part 1: recent dates off by one day). That report is filed against the TUI /stats; this is the desktop app dashboard, so the bucketing logic may be shared.

Possible secondary effect: Active days also seemed to apply a minimum-activity threshold — very light days (a few dozen messages) didn't count as active, further lowering the total. Lower confidence; noticed while reconciling the numbers.

Suggested fix: bucket the heatmap and streak/active-day calculations by the user's local timezone (or make the timezone explicit/configurable), using per-message timestamps.

wenmco · 1 month ago

Follow-up with a concrete, reproducible data point on macOS that narrows this down — the desktop app's own store contains the sessions for a day the heatmap shows as empty.

On this machine there are two stores:

  1. CLI/shared transcripts: ~/.claude/projects/<project>/<session-uuid>.jsonl
  2. Desktop app session index: ~/Library/Application Support/Claude/claude-code-sessions/<workspace-id>/<id>/local_<uuid>.json — small per-session metadata records (cliSessionId, createdAt, lastActivityAt, cwd, completedTurns, model…) that reference the CLI session.

For one heavily-used afternoon (local America/New_York, UTC−4):

  • The transcript JSONL holds 1,114 messages with per-message timestamps spanning 14:45–19:34 local that day.
  • The desktop store holds matching session records for the same day (local_*.json written 14:59 and 20:08 local, with createdAt/lastActivityAt on that date).

So the desktop app has that day's sessions in its own directory, yet the activity dashboard renders the day as empty / doesn't credit the streak. That rules out a data-capture problem and points squarely at the aggregation layer — whatever reads these records into the heatmap is either bucketing by the UTC timestamp (see above) or keying off the wrong date field, rather than crediting each local calendar day the records actually cover.

Repro sketch (no session content needed): compare createdAt/lastActivityAt/per-message timestamps in the stores above against the day each session is credited to in the dashboard; on a negative-UTC-offset timezone, afternoon/evening-local sessions land on the wrong day or drop out.

SolomonKun-hub · 1 month ago

Another data point confirming the session-start-date attribution bug.

My dashboard currently shows Current streak: 0d, while Active days: 30 and Longest streak: 30d are equal — which by itself proves the streak was never actually broken in the account's history.

Local session logs (~/.claude/projects/**/*.jsonl) show activity on every single calendar day. The day the dashboard treats as empty is 2026-07-07: ~976 messages were sent that day (from 09:00 local to 00:44 the next night, UTC+6), but all of them inside a session that was started on 2026-07-06. No new session was created on 07-07, so the dashboard credited two full days of work to 07-06, rendered 07-07 as inactive on the heatmap, and reset the current streak to 0.

Message timestamps inside the spanning session file, grouped by UTC hour, show solid activity blocks on 2026-07-07 at 03–04, 09–11, and 17–18 UTC (several hundred messages) — yet the day counts as empty.

Attributing activity by per-message timestamps instead of session creation date would fix Active days, the heatmap, streaks, and peak hour all at once.

jackycyangfox-commits · 1 month ago

Same issue here, and I can add a verified reproduction with data.

Environment: Windows desktop app 1.22209.0.0 (bundled CLI 2.1.209), Windows 10, UTC+8.

On Jul 16 I used Claude Code for hours (~251 messages timestamped Jul 16 in my local ~/.claude/projects transcripts — all within Jul 16 in UTC too, so not the UTC-bucketing issue). But all of it was in two resumed sessions (started Jun 30 and Jul 14), and no new session was created that day. The dashboard shows "Jul 16 — 0" and my streak reset to 0d.

I replayed the stats algorithm against my local transcripts, and every dashboard number matches session-start-date bucketing:

  • "Active days 12" = exactly my 12 distinct days on which a new session was started (message-date bucketing would give ~20)
  • "Longest streak 2d" = consecutive new-session days Jul 13–14 (message-date bucketing would give 3: Jul 5–7)
  • "Jul 16 — 0" despite ~251 messages that day

Note the CLI /stats scan in recent builds already buckets messages by their own timestamp (dailyActivity[date].messageCount++ per message), so the desktop dashboard is inconsistent with the CLI. Users who keep one long-running session per project and resume it daily end up with empty heatmaps and permanent 0-day streaks.

giovanniacg · 1 month ago

Confirming this on Claude Code 2.1.216 (macOS, arm64), and it is especially punishing for always-on / daemon setups.

I run a long-lived background agent (Claude Agent SDK daemon) that keeps sessions open for days. My "Longest session" reads 16d 23h. Because activity is credited to the session-start calendar day, whole days worked entirely inside a resumed / long-running session show 0 in dailyActivity, and a single such gap resets the streak.

Concrete repro from my own ~/.claude/stats-cache.json (I diagnosed it by reconstructing per-day event counts from the session JSONL timestamps):

  • I worked continuously across Fri -> Sat in local time (America/Sao_Paulo, UTC-3).
  • Last event Friday fell at 20:03 UTC (still day 24). First "Saturday" event landed at 00:19 UTC (day 26).
  • Day 25 UTC ended up with zero events in dailyActivity, even though I worked that entire local calendar day.
  • Result: a 76-day streak reset to 1.

There are two compounding root causes here:

  1. Activity is bucketed by session-start date, not per active calendar day (this issue).
  2. The bucketing is done in UTC (getUTCDate / toISOString are what the bundle uses) instead of the user's local timezone, so evening work for UTC-negative users leaks into the next day.

Suggested fix: bucket each event by its own timestamp, in the user's local/system timezone, rather than by session start and rather than in UTC. That single change fixes both the multi-day-session breakage and the timezone-boundary breakage.

Showing cached comments. Read the full discussion on GitHub ↗