diff --git a/internal/cmd/crew_at.go b/internal/cmd/crew_at.go index 18b75c3b..c1494db9 100644 --- a/internal/cmd/crew_at.go +++ b/internal/cmd/crew_at.go @@ -83,6 +83,37 @@ func runCrewAt(cmd *cobra.Command, args []string) error { return fmt.Errorf("checking session: %w", err) } + // Before creating a new session, check if there's already a Claude session + // running in this crew's directory (might have been started manually or via + // a different mechanism) + if !hasSession { + existingSessions, err := t.FindSessionByWorkDir(worker.ClonePath, true) + if err == nil && len(existingSessions) > 0 { + // Found an existing session with Claude running in this directory + existingSession := existingSessions[0] + fmt.Printf("%s Found existing Claude session '%s' in crew directory\n", + style.Warning.Render("⚠"), + existingSession) + fmt.Printf(" Attaching to existing session instead of creating a new one\n") + + // If inside tmux (but different session), inform user + if tmux.IsInsideTmux() { + fmt.Printf("Use C-b s to switch to '%s'\n", existingSession) + return nil + } + + // Outside tmux: attach unless --detached flag is set + if crewDetached { + fmt.Printf("Existing session: '%s'. Run 'tmux attach -t %s' to attach.\n", + existingSession, existingSession) + return nil + } + + // Attach to existing session + return attachToTmuxSession(existingSession) + } + } + if !hasSession { // Create new session if err := t.NewSession(sessionID, worker.ClonePath); err != nil { diff --git a/internal/tmux/tmux.go b/internal/tmux/tmux.go index c04b0dd9..d29e6404 100644 --- a/internal/tmux/tmux.go +++ b/internal/tmux/tmux.go @@ -292,6 +292,42 @@ func (t *Tmux) GetPaneWorkDir(session string) (string, error) { return strings.TrimSpace(out), nil } +// FindSessionByWorkDir finds tmux sessions where the pane's current working directory +// matches or is under the target directory. Returns session names that match. +// If checkClaude is true, only returns sessions that have Claude (node) running. +func (t *Tmux) FindSessionByWorkDir(targetDir string, checkClaude bool) ([]string, error) { + sessions, err := t.ListSessions() + if err != nil { + return nil, err + } + + var matches []string + for _, session := range sessions { + if session == "" { + continue + } + + workDir, err := t.GetPaneWorkDir(session) + if err != nil { + continue // Skip sessions we can't query + } + + // Check if workdir matches target (exact match or subdir) + if workDir == targetDir || strings.HasPrefix(workDir, targetDir+"/") { + if checkClaude { + // Only include if Claude is running + if t.IsClaudeRunning(session) { + matches = append(matches, session) + } + } else { + matches = append(matches, session) + } + } + } + + return matches, nil +} + // CapturePane captures the visible content of a pane. func (t *Tmux) CapturePane(session string, lines int) (string, error) { return t.run("capture-pane", "-p", "-t", session, "-S", fmt.Sprintf("-%d", lines))