Skip to content

Commit 92321a6

Browse files
yepzdkclaude
andcommitted
Add git branch display and sandbox status indicator
- Display git branch inline as @Branch (truncated to 12 chars) - Show [!S] indicator when session has used unsandboxed commands - Extract gitBranch from log entries for each session - Parse Bash tool_use inputs to detect dangerouslyDisableSandbox usage - Increase log file buffer from 1MB to 10MB to handle large entries - Reorder indicators: branch > ghost > unsandboxed > desktop Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cd566c8 commit 92321a6

File tree

2 files changed

+97
-27
lines changed

2 files changed

+97
-27
lines changed

internal/session/session.go

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ const (
2727

2828
// Session represents a Claude Code session
2929
type Session struct {
30-
Project string `json:"project"`
31-
Status Status `json:"status"`
32-
LastActivity time.Time `json:"last_activity"`
33-
Task string `json:"task"`
34-
Summary string `json:"summary,omitempty"`
35-
LastMessage string `json:"last_message,omitempty"`
36-
LogFile string `json:"-"`
37-
ProjectPath string `json:"-"` // Full path to the project directory
38-
IsDesktop bool `json:"is_desktop,omitempty"` // True if session appears to be from desktop app
39-
IsGhost bool `json:"is_ghost,omitempty"` // True if process running but log is stale
40-
GhostPID int `json:"ghost_pid,omitempty"` // PID of the ghost process (for killing)
30+
Project string `json:"project"`
31+
Status Status `json:"status"`
32+
LastActivity time.Time `json:"last_activity"`
33+
Task string `json:"task"`
34+
Summary string `json:"summary,omitempty"`
35+
LastMessage string `json:"last_message,omitempty"`
36+
LogFile string `json:"-"`
37+
ProjectPath string `json:"-"` // Full path to the project directory
38+
IsDesktop bool `json:"is_desktop,omitempty"` // True if session appears to be from desktop app
39+
IsGhost bool `json:"is_ghost,omitempty"` // True if process running but log is stale
40+
GhostPID int `json:"ghost_pid,omitempty"` // PID of the ghost process (for killing)
41+
GitBranch string `json:"git_branch,omitempty"` // Current git branch
42+
HasUnsandboxed bool `json:"has_unsandboxed,omitempty"` // True if any command bypassed sandbox
4143
}
4244

4345
// RunningProcess represents a Claude process with its PID and working directory
@@ -53,6 +55,7 @@ type LogEntry struct {
5355
Timestamp time.Time `json:"timestamp"`
5456
Message *Message `json:"message,omitempty"`
5557
Summary string `json:"summary,omitempty"` // For type: "summary" entries
58+
GitBranch string `json:"gitBranch,omitempty"`
5659
}
5760

5861
// Message represents the message field in a log entry
@@ -63,9 +66,16 @@ type Message struct {
6366

6467
// ContentItem represents an item in the content array
6568
type ContentItem struct {
66-
Type string `json:"type"`
67-
Text string `json:"text,omitempty"`
68-
Name string `json:"name,omitempty"` // For tool_use
69+
Type string `json:"type"`
70+
Text string `json:"text,omitempty"`
71+
Name string `json:"name,omitempty"` // For tool_use
72+
Input json.RawMessage `json:"input,omitempty"` // For tool_use inputs
73+
}
74+
75+
// BashToolInput represents the input for a Bash tool_use entry
76+
type BashToolInput struct {
77+
Command string `json:"command"`
78+
DangerouslyDisableSandbox bool `json:"dangerouslyDisableSandbox"`
6979
}
7080

7181
// ClaudeProjectsDir returns the path to the Claude projects directory
@@ -293,6 +303,12 @@ func parseSession(projectName, logFile string, runningDirs map[string]int) (Sess
293303
// Extract last assistant message text
294304
session.LastMessage = extractLastAssistantMessage(entries)
295305

306+
// Extract git branch (use most recent non-empty)
307+
session.GitBranch = extractGitBranch(entries)
308+
309+
// Detect if any commands ran without sandbox
310+
session.HasUnsandboxed = detectUnsandboxedCommands(entries)
311+
296312
// Determine status from log entries
297313
session.Status, session.Task, session.IsGhost = determineStatus(entries, isRunning)
298314

@@ -381,6 +397,36 @@ func extractLastAssistantMessage(entries []LogEntry) string {
381397
return ""
382398
}
383399

400+
// extractGitBranch extracts the most recent git branch from entries
401+
func extractGitBranch(entries []LogEntry) string {
402+
for i := len(entries) - 1; i >= 0; i-- {
403+
if entries[i].GitBranch != "" {
404+
return entries[i].GitBranch
405+
}
406+
}
407+
return ""
408+
}
409+
410+
// detectUnsandboxedCommands checks if any Bash commands ran with sandbox disabled
411+
func detectUnsandboxedCommands(entries []LogEntry) bool {
412+
for _, entry := range entries {
413+
if entry.Type != "assistant" || entry.Message == nil {
414+
continue
415+
}
416+
for _, content := range entry.Message.Content {
417+
if content.Type == "tool_use" && content.Name == "Bash" && len(content.Input) > 0 {
418+
var input BashToolInput
419+
if json.Unmarshal(content.Input, &input) == nil {
420+
if input.DangerouslyDisableSandbox {
421+
return true
422+
}
423+
}
424+
}
425+
}
426+
}
427+
return false
428+
}
429+
384430
// decodeProjectName converts the directory name to a readable project name
385431
func decodeProjectName(name string) string {
386432
// Format: -Users-username-Projects-org-project
@@ -430,9 +476,9 @@ func readLastEntries(filePath string, count int) ([]LogEntry, error) {
430476

431477
var entries []LogEntry
432478
scanner := bufio.NewScanner(file)
433-
// Increase buffer size for long lines
479+
// Increase buffer size for very long lines (some entries can be several MB)
434480
buf := make([]byte, 0, 64*1024)
435-
scanner.Buffer(buf, 1024*1024)
481+
scanner.Buffer(buf, 10*1024*1024) // 10MB max
436482

437483
for scanner.Scan() {
438484
line := scanner.Text()

internal/ui/ui.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -255,21 +255,45 @@ func truncate(s string, max int) string {
255255
// formatProject formats the project name with optional indicators
256256
func formatProject(s session.Session, maxLen int) string {
257257
name := s.Project
258+
var suffixes []string
259+
suffixLen := 0
260+
261+
// Add git branch if present (show first, most useful)
262+
if s.GitBranch != "" {
263+
branch := s.GitBranch
264+
if len(branch) > 12 {
265+
branch = branch[:12]
266+
}
267+
suffixes = append(suffixes, Dim+"@"+branch+Reset)
268+
suffixLen += 1 + len(branch) // @branch
269+
}
258270

259-
// Ghost indicator takes priority (more important to show)
271+
// Ghost indicator (highest priority warning)
260272
if s.IsGhost {
261-
indicator := Red + " [ghost]" + Reset
262-
// Account for indicator in truncation (8 visible chars)
263-
truncated := truncate(name, maxLen-8)
264-
return truncated + indicator
273+
suffixes = append(suffixes, Red+"[ghost]"+Reset)
274+
suffixLen += 8 // " [ghost]"
275+
}
276+
277+
// Unsandboxed indicator (security warning)
278+
if s.HasUnsandboxed {
279+
suffixes = append(suffixes, Yellow+"[!S]"+Reset)
280+
suffixLen += 5 // " [!S]"
265281
}
266282

283+
// Desktop indicator (lowest priority)
267284
if s.IsDesktop {
268-
// Add subtle desktop indicator
269-
indicator := Dim + " [D]" + Reset
270-
// Account for indicator in truncation (4 visible chars)
271-
truncated := truncate(name, maxLen-4)
272-
return truncated + indicator
285+
suffixes = append(suffixes, Dim+"[D]"+Reset)
286+
suffixLen += 4 // " [D]"
273287
}
274-
return truncate(name, maxLen)
288+
289+
// Truncate name to fit with suffixes
290+
truncated := truncate(name, maxLen-suffixLen-len(suffixes)) // -len for spaces
291+
292+
// Build result
293+
result := truncated
294+
for _, suffix := range suffixes {
295+
result += " " + suffix
296+
}
297+
298+
return result
275299
}

0 commit comments

Comments
 (0)