Skip to content

Commit 416e43a

Browse files
jedardenclaude
andcommitted
feat: add running process count for better instance detection
- Add RunningProcesses field to TmuxMetrics - Count claude processes via pgrep for reliable detection - Display "tracked/total procs" when counts differ - Helps identify detection gaps when hooks aren't working Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 47c600f commit 416e43a

2 files changed

Lines changed: 38 additions & 9 deletions

File tree

internal/metrics/tmux.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ type TmuxSession struct {
7878

7979
// TmuxMetrics holds information about all tmux sessions
8080
type TmuxMetrics struct {
81-
Sessions []TmuxSession `json:"sessions"`
82-
Total int `json:"total"`
83-
Available bool `json:"available"`
84-
Error string `json:"error,omitempty"`
85-
LastUpdate time.Time `json:"last_update"`
86-
HooksAvailable bool `json:"hooks_available"` // Whether hook-based tracking is active
87-
HooksInstalled bool `json:"hooks_installed"` // Whether hooks are installed
88-
Source string `json:"source"` // "hooks", "tmux", or "hybrid"
81+
Sessions []TmuxSession `json:"sessions"`
82+
Total int `json:"total"`
83+
Available bool `json:"available"`
84+
Error string `json:"error,omitempty"`
85+
LastUpdate time.Time `json:"last_update"`
86+
HooksAvailable bool `json:"hooks_available"` // Whether hook-based tracking is active
87+
HooksInstalled bool `json:"hooks_installed"` // Whether hooks are installed
88+
Source string `json:"source"` // "hooks", "tmux", or "hybrid"
89+
RunningProcesses int `json:"running_processes"` // Number of running claude processes
8990
}
9091

9192
// TmuxCollector collects metrics about tmux sessions
@@ -227,6 +228,7 @@ func (tc *TmuxCollector) Collect() *TmuxMetrics {
227228

228229
metrics.Available = hasTmux || hasHooks
229230
metrics.Total = len(metrics.Sessions)
231+
metrics.RunningProcesses = tc.countRunningClaudeProcesses()
230232

231233
if !metrics.Available && !tc.isTmuxAvailable() {
232234
metrics.Error = "tmux is not installed or not available in PATH"
@@ -572,6 +574,28 @@ func (tc *TmuxCollector) fallbackStatus(session TmuxSession, now time.Time) Sess
572574
return StatusReady
573575
}
574576

577+
// countRunningClaudeProcesses counts the number of running claude processes
578+
// This provides a reliable count independent of hooks or tmux
579+
func (tc *TmuxCollector) countRunningClaudeProcesses() int {
580+
ctx, cancel := context.WithTimeout(context.Background(), tmuxCommandTimeout)
581+
defer cancel()
582+
583+
// Use pgrep to count claude processes (exact match to avoid false positives)
584+
cmd := exec.CommandContext(ctx, "pgrep", "-x", "claude")
585+
var stdout bytes.Buffer
586+
cmd.Stdout = &stdout
587+
588+
_ = cmd.Run() // Ignore error - pgrep returns 1 if no matches
589+
590+
output := strings.TrimSpace(stdout.String())
591+
if output == "" {
592+
return 0
593+
}
594+
595+
// Count lines (each line is a PID)
596+
return len(strings.Split(output, "\n"))
597+
}
598+
575599
// GetMetrics is a convenience method that returns the collected metrics
576600
func (tc *TmuxCollector) GetMetrics() *TmuxMetrics {
577601
return tc.Collect()

internal/ui/dashboard.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,12 @@ func (d *Dashboard) renderTmuxPanel(width, height int) string {
12151215
// Hooks installed but no hook sessions, falling back to tmux
12161216
sourceLabel = "Sessions (tmux)"
12171217
}
1218-
title := successStyle.Render(fmt.Sprintf("%s %s (%d)", sourceIcon, sourceLabel, d.tmuxMetrics.Total))
1218+
// Show process count if different from tracked sessions (indicates detection gap)
1219+
countStr := fmt.Sprintf("%d", d.tmuxMetrics.Total)
1220+
if d.tmuxMetrics.RunningProcesses > 0 && d.tmuxMetrics.RunningProcesses != d.tmuxMetrics.Total {
1221+
countStr = fmt.Sprintf("%d/%d procs", d.tmuxMetrics.Total, d.tmuxMetrics.RunningProcesses)
1222+
}
1223+
title := successStyle.Render(fmt.Sprintf("%s %s (%s)", sourceIcon, sourceLabel, countStr))
12191224
titleLen := lipgloss.Width(title)
12201225
summaryLen := lipgloss.Width(statusSummary)
12211226
spacing := contentWidth - titleLen - summaryLen

0 commit comments

Comments
 (0)