claude-code - 💡(How to fix) Fix Stats dashboard counts multi-day sessions only on their start date (Active days / streaks / peak hour undercounted)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The desktop client's Stats dashboard (Overview tab 鈥?Active days, Current/Longest streak, Peak hour, and the activity heatmap) buckets each entire session under the date/hour of its first message only. Any session that spans multiple calendar days (a long-running or resumed/forked conversation) is counted as activity on its start day alone; every subsequent day silently disappears from these metrics.

Root Cause

In the stats aggregation routine, each transcript file's day/hour bucket is derived from the first message's timestamp, and the whole session is attributed to that single bucket:

// per transcript file:
const v   = new Date(messages[0].timestamp);   // only the FIRST message
const day = formatLocalDate(v);                // one bucket for the ENTIRE session
dayEntry.sessionCount++;
dayEntry.messageCount += messages.length;       // all messages -> start day
hourCounts[v.getHours()]++;                     // peak hour uses the START hour

// later:
activeDays = new Set(dailyActivity.map(d => d.date)).size;  // = number of distinct *start* days
streaks    = computeStreaks(thatSameSet);

Because a resumed/forked session copies the original history, multiple multi-day branches share the same first-message timestamp, so several of them collapse onto a single start day, compounding the undercount.

Code Example

// per transcript file:
const v   = new Date(messages[0].timestamp);   // only the FIRST message
const day = formatLocalDate(v);                // one bucket for the ENTIRE session
dayEntry.sessionCount++;
dayEntry.messageCount += messages.length;       // all messages -> start day
hourCounts[v.getHours()]++;                     // peak hour uses the START hour

// later:
activeDays = new Set(dailyActivity.map(d => d.date)).size;  // = number of distinct *start* days
streaks    = computeStreaks(thatSameSet);
RAW_BUFFERClick to expand / collapse

Summary

The desktop client's Stats dashboard (Overview tab 鈥?Active days, Current/Longest streak, Peak hour, and the activity heatmap) buckets each entire session under the date/hour of its first message only. Any session that spans multiple calendar days (a long-running or resumed/forked conversation) is counted as activity on its start day alone; every subsequent day silently disappears from these metrics.

Environment

  • Claude desktop (Windows, MSIX): Claude_1.11187.1.0_x64
  • Bundled Claude Code CLI: 2.1.161
  • OS: Windows 11, local timezone UTC+8

Symptoms (real example)

On a machine with 7 sessions / 5,570 messages whose transcript activity actually spans 8 consecutive local days (2026-05-30 鈫?2026-06-06):

MetricShownCorrect
Active days28
Longest streak1d8d
Current streak0d8d
Peak hourhour sessions were startedshould reflect when messages actually occurred
Heatmaponly 2 cells lit8 cells

Cross-check: Total tokens, Messages, Sessions, and Favorite model are correct, because those are accumulated per-message. Only the day/hour-bucketed metrics are wrong.

Root cause

In the stats aggregation routine, each transcript file's day/hour bucket is derived from the first message's timestamp, and the whole session is attributed to that single bucket:

// per transcript file:
const v   = new Date(messages[0].timestamp);   // only the FIRST message
const day = formatLocalDate(v);                // one bucket for the ENTIRE session
dayEntry.sessionCount++;
dayEntry.messageCount += messages.length;       // all messages -> start day
hourCounts[v.getHours()]++;                     // peak hour uses the START hour

// later:
activeDays = new Set(dailyActivity.map(d => d.date)).size;  // = number of distinct *start* days
streaks    = computeStreaks(thatSameSet);

Because a resumed/forked session copies the original history, multiple multi-day branches share the same first-message timestamp, so several of them collapse onto a single start day, compounding the undercount.

Expected behavior

Day/hour bucketing should be derived from each message's own timestamp, not the session's first message 鈥?i.e. iterate the messages and increment the bucket for formatLocalDate(message.timestamp) (and message.timestamp.getHours() for peak hour). Then activeDays, the streaks, the heatmap, and peak hour will reflect actual per-day / per-hour activity.

Note: stats-cache.json would need to be invalidated on upgrade, since the cached dailyActivity is already mis-bucketed and the incremental updater would otherwise keep the stale per-day rows.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Day/hour bucketing should be derived from each message's own timestamp, not the session's first message 鈥?i.e. iterate the messages and increment the bucket for formatLocalDate(message.timestamp) (and message.timestamp.getHours() for peak hour). Then activeDays, the streaks, the heatmap, and peak hour will reflect actual per-day / per-hour activity.

Note: stats-cache.json would need to be invalidated on upgrade, since the cached dailyActivity is already mis-bucketed and the incremental updater would otherwise keep the stale per-day rows.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING