Skip to content

Commit 9ec1c07

Browse files
fix(mcp): correct project name and cost time-bucket bugs from wave 1
- health_tools: use resolveProjectName instead of filepath.Base for the active session project name; filepath.Base returned the raw hash dir (e.g. -Users-dayna-blackwell-code-commitmux) not the friendly name - cost_tools: anchor today/week bucket logic on last UserMessageTimestamp instead of StartTime so resumed multi-day sessions count toward the correct time buckets; falls back to StartTime when timestamps are empty
1 parent 93b4b68 commit 9ec1c07

3 files changed

Lines changed: 21 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ All notable changes to claudewatch are documented here.
88

99
- **`get_cost_summary` live session gap** — the current in-progress session was invisible to cost aggregates, causing a ~$212 hole in today/week/all-time totals and by-project breakdowns. `handleGetCostSummary` now calls `FindActiveSessionPath` + `ParseActiveSession` after loading indexed sessions, deduplicates by SessionID to prevent double-counting if the session closes between calls, and applies the same time-bucket and by-project logic as indexed sessions. Non-fatal: any active session error falls through to indexed-only path.
1010

11-
- **`get_project_health` wrong default** — with no `project` arg the tool sorted indexed sessions by `StartTime` and picked the most recent closed session, which was wrong when a session was actively running. The default now checks for an active session first via `FindActiveSessionPath` + `ParseActiveSession`, uses `filepath.Base(meta.ProjectPath)` as the project name, and falls back to the existing sort-by-StartTime logic only when no active session is available. Priority: explicit arg > active session > most-recent indexed session.
11+
- **`get_project_health` wrong default** — with no `project` arg the tool sorted indexed sessions by `StartTime` and picked the most recent closed session, which was wrong when a session was actively running. The default now checks for an active session first via `FindActiveSessionPath` + `ParseActiveSession`, resolves the project name via `resolveProjectName` (not `filepath.Base`, which returned the raw hash directory), and falls back to the existing sort-by-StartTime logic only when no active session is available. Priority: explicit arg > active session > most-recent indexed session.
12+
13+
- **`get_project_health` active-session project name**`filepath.Base(meta.ProjectPath)` returned the hashed directory name (e.g. `-Users-dayna-blackwell-code-commitmux`) instead of the friendly project name. Fixed by using `resolveProjectName(meta.SessionID, meta.ProjectPath, tags)`, consistent with how indexed sessions resolve names.
14+
15+
- **`get_cost_summary` today/week undercounting for resumed sessions** — time-bucket logic used `session.StartTime` to decide whether a session counted toward `today_usd` or `week_usd`. Long-running sessions resumed across day or week boundaries had a start time in the past, causing their cost to appear in neither bucket. Fixed by anchoring on the last entry in `UserMessageTimestamps` (most recent user activity), falling back to `StartTime` only when the timestamps list is empty.
1216

1317
### Added
1418

internal/mcp/cost_tools.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (s *Server) handleGetCostSummary(args json.RawMessage) (any, error) {
6868
cost := analyzer.EstimateSessionCost(session, pricing, ratio)
6969
allTimeUSD += cost
7070

71-
t := claude.ParseTimestamp(session.StartTime)
71+
t := lastActiveTime(session.UserMessageTimestamps, session.StartTime)
7272
if !t.IsZero() {
7373
tUTC := t.UTC()
7474
if tUTC.Format("2006-01-02") == todayStr {
@@ -98,7 +98,7 @@ func (s *Server) handleGetCostSummary(args json.RawMessage) (any, error) {
9898
liveCost := analyzer.EstimateSessionCost(*liveMeta, pricing, ratio)
9999
allTimeUSD += liveCost
100100

101-
t := claude.ParseTimestamp(liveMeta.StartTime)
101+
t := lastActiveTime(liveMeta.UserMessageTimestamps, liveMeta.StartTime)
102102
if !t.IsZero() {
103103
tUTC := t.UTC()
104104
if tUTC.Format("2006-01-02") == todayStr {
@@ -145,3 +145,16 @@ func (s *Server) handleGetCostSummary(args json.RawMessage) (any, error) {
145145
ByProject: projectSpends,
146146
}, nil
147147
}
148+
149+
// lastActiveTime returns the timestamp of the most recent user message in the session,
150+
// falling back to startTime if UserMessageTimestamps is empty. This avoids misclassifying
151+
// long-running resumed sessions as inactive on their original start date.
152+
func lastActiveTime(userMsgTimestamps []string, startTime string) time.Time {
153+
for i := len(userMsgTimestamps) - 1; i >= 0; i-- {
154+
t := claude.ParseTimestamp(userMsgTimestamps[i])
155+
if !t.IsZero() {
156+
return t
157+
}
158+
}
159+
return claude.ParseTimestamp(startTime)
160+
}

internal/mcp/health_tools.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (s *Server) handleGetProjectHealth(args json.RawMessage) (any, error) {
6161
if activeErr == nil && activePath != "" {
6262
meta, parseErr := claude.ParseActiveSession(activePath)
6363
if parseErr == nil && meta != nil && meta.ProjectPath != "" {
64-
project = filepath.Base(meta.ProjectPath)
64+
project = resolveProjectName(meta.SessionID, meta.ProjectPath, tags)
6565
}
6666
}
6767

0 commit comments

Comments
 (0)