Skip to content

Instantly share code, notes, and snippets.

@nazq
Last active November 26, 2025 15:34
Show Gist options
  • Select an option

  • Save nazq/3d179988627b15119944da573c91f1ab to your computer and use it in GitHub Desktop.

Select an option

Save nazq/3d179988627b15119944da573c91f1ab to your computer and use it in GitHub Desktop.
Claude Session Manager - tmux session manager for Claude Code
#!/bin/bash
# Claude Session Manager for Git Worktrees
# Usage: claude_session [session_name] [--attach]
# Alias: cs
claude_session() {
local session_name=""
local auto_attach=false
local custom_name=""
local list_sessions=false
local attach_number=""
local kill_number=""
local continue_mode=false
local dangerous_mode=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--list|-l)
list_sessions=true
shift
;;
--kill|-k)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
kill_number="$2"
shift 2
else
echo "❌ --kill requires a session number"
echo "Use 'cs -l' to see numbered sessions"
return 1
fi
;;
--attach|-a)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
attach_number="$2"
shift 2
else
auto_attach=true
shift
fi
;;
--continue|-c)
continue_mode=true
shift
;;
--dangerous|-x)
dangerous_mode=true
shift
;;
--help|-h)
echo "Usage: claude_session [session_name] [--attach [number]] [--continue] [--dangerous] [--list] [--kill number]"
echo ""
echo "Creates a tmux session with Claude Code running"
echo ""
echo "Options:"
echo " session_name Custom session name (optional)"
echo " --attach, -a Automatically attach to session after creation"
echo " --attach NUM, -a NUM Attach to session number NUM from list"
echo " --continue, -c Start Claude with --continue flag"
echo " --dangerous, -x Start Claude with --dangerously-skip-permissions --permission-mode acceptEdits"
echo " --list, -l List all active tmux sessions with numbers"
echo " --kill NUM, -k NUM Kill session number NUM from list"
echo " --help, -h Show this help message"
echo ""
echo "Session naming logic:"
echo " - In git worktree: REPO:BRANCH"
echo " - Not in git worktree: FOLDER_NAME"
echo " - Custom name overrides automatic naming"
echo ""
echo "Examples:"
echo " cs # Create session with auto-detected name"
echo " cs -l # List all sessions with numbers"
echo " cs -a 3 # Attach to session #3 from the list"
echo " cs -k 2 # Kill session #2 from the list"
echo " cs my-project -a # Create 'my-project' session and attach"
echo " cs -c -a # Create session with --continue and attach"
echo " cs -x -a # Create session with dangerous mode (skip permissions) and attach"
return 0
;;
*)
custom_name="$1"
shift
;;
esac
done
# Handle list sessions
if [[ "$list_sessions" == true ]]; then
echo "πŸ“‹ Active tmux sessions:"
local sessions=($(tmux list-sessions -F "#{session_name}" 2>/dev/null | sort))
if [[ ${#sessions[@]} -eq 0 ]]; then
echo " No active sessions found"
return 0
fi
local i=1
for session in "${sessions[@]}"; do
local session_info=$(tmux list-sessions | grep "^$session:" | head -1)
printf "%2d. %s\n" "$i" "$session_info"
((i++))
done
return 0
fi
# Handle kill by number
if [[ -n "$kill_number" ]]; then
local sessions=($(tmux list-sessions -F "#{session_name}" 2>/dev/null | sort))
if [[ ${#sessions[@]} -eq 0 ]]; then
echo "❌ No active sessions found"
return 1
fi
if [[ "$kill_number" -lt 1 || "$kill_number" -gt ${#sessions[@]} ]]; then
echo "❌ Invalid session number: $kill_number"
echo "πŸ“‹ Available sessions (1-${#sessions[@]}):"
claude_session --list
return 1
fi
local target_session="${sessions[$((kill_number-1))]}"
echo "πŸ’€ Killing session #$kill_number: $target_session"
tmux kill-session -t "$target_session"
if [[ $? -eq 0 ]]; then
echo "βœ… Session killed successfully"
else
echo "❌ Failed to kill session"
fi
return 0
fi
# Handle attach by number
if [[ -n "$attach_number" ]]; then
local sessions=($(tmux list-sessions -F "#{session_name}" 2>/dev/null | sort))
if [[ ${#sessions[@]} -eq 0 ]]; then
echo "❌ No active sessions found"
return 1
fi
if [[ "$attach_number" -lt 1 || "$attach_number" -gt ${#sessions[@]} ]]; then
echo "❌ Invalid session number: $attach_number"
echo "πŸ“‹ Available sessions (1-${#sessions[@]}):"
claude_session --list
return 1
fi
local target_session="${sessions[$((attach_number-1))]}"
echo "πŸ”— Attaching to session #$attach_number: $target_session"
tmux attach-session -t "$target_session"
return 0
fi
# Continue with normal session creation logic...
# Determine session name
if [[ -n "$custom_name" ]]; then
session_name="$custom_name"
echo "🎯 Using custom session name: $session_name"
else
# Check if we're in a git worktree
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
# Get repository name (from remote or folder name)
local repo_name=""
local remote_url=$(git remote get-url origin 2>/dev/null)
if [[ -n "$remote_url" ]]; then
# Extract repo name from remote URL
repo_name=$(basename "$remote_url" .git)
else
# Fallback to git root directory name
repo_name=$(basename "$(git rev-parse --show-toplevel)")
fi
# Get current branch name
local branch_name=$(git branch --show-current 2>/dev/null)
if [[ -z "$branch_name" ]]; then
# Fallback for detached HEAD
branch_name=$(git rev-parse --short HEAD 2>/dev/null || echo "detached")
fi
session_name="${repo_name}:${branch_name}"
echo "πŸ“ Git worktree detected: $session_name"
else
# Use current directory name
session_name=$(basename "$PWD")
echo "πŸ“‚ Using directory name: $session_name"
fi
fi
# Sanitize session name for tmux (tmux converts : and . to _)
local original_name="$session_name"
session_name=$(echo "$session_name" | sed 's/[:.]/_/g' | sed 's/[^a-zA-Z0-9_-]/_/g')
if [[ "$session_name" != "$original_name" ]]; then
echo "πŸ”§ Sanitized session name: $original_name β†’ $session_name"
fi
# Check if session already exists
if tmux has-session -t "$session_name" 2>/dev/null; then
echo "⚑ Session '$session_name' already exists!"
read -p "Do you want to [a]ttach, [k]ill and recreate, or [c]ancel? (a/k/c): " choice
case $choice in
a|A|"")
echo "πŸ”— Attaching to existing session..."
tmux attach-session -t "$session_name"
return 0
;;
k|K)
echo "πŸ’€ Killing existing session..."
tmux kill-session -t "$session_name"
;;
c|C)
echo "❌ Cancelled"
return 0
;;
*)
echo "❌ Invalid choice. Cancelled."
return 1
;;
esac
fi
# Warn if dangerous mode is enabled
if [[ "$dangerous_mode" == true ]]; then
echo "⚠️ WARNING: Dangerous mode enabled!"
echo " Claude will run with --dangerously-skip-permissions --permission-mode acceptEdits"
echo " This skips all permission prompts and auto-accepts edits."
fi
# Create tmux session
echo "πŸš€ Creating tmux session: $session_name"
echo "πŸ“ Directory: $PWD"
# Create detached session in current directory
tmux new-session -d -s "$session_name" -c "$PWD"
# Give tmux a moment to create the session
sleep 0.5
# Check if tmux session was created successfully
if ! tmux has-session -t "$session_name" 2>/dev/null; then
echo "❌ Failed to create tmux session '$session_name'"
echo "πŸ” Checking if tmux created it with a different name..."
echo "πŸ“‹ Current sessions:"
tmux list-sessions 2>/dev/null
return 1
fi
# Configure the session
tmux send-keys -t "$session_name" "clear" C-m
tmux send-keys -t "$session_name" "echo 'πŸ€– Starting Claude Code session for: $session_name'" C-m
tmux send-keys -t "$session_name" "echo 'πŸ“ Directory: $PWD'" C-m
# Check if claude command exists
if ! command -v claude >/dev/null 2>&1; then
echo "⚠️ Warning: 'claude' command not found"
echo " Make sure Claude Code is installed and in your PATH"
tmux send-keys -t "$session_name" "echo 'Warning: claude command not found. Please install Claude Code.'" C-m
else
# Start Claude
echo "πŸ€– Starting Claude Code..."
local claude_cmd="claude"
if [[ "$continue_mode" == true ]]; then
claude_cmd="$claude_cmd --continue"
fi
if [[ "$dangerous_mode" == true ]]; then
claude_cmd="$claude_cmd --dangerously-skip-permissions --permission-mode acceptEdits"
fi
tmux send-keys -t "$session_name" "$claude_cmd" C-m
fi
# Set window name
tmux rename-window -t "$session_name:0" "claude"
echo "βœ… Session created successfully!"
echo "πŸ”— Connect with: tmux attach -t '$session_name'"
# Auto-attach if requested
if [[ "$auto_attach" == true ]]; then
echo "πŸ”— Auto-attaching to session..."
sleep 1 # Brief pause to let Claude start
tmux attach-session -t "$session_name"
fi
}
# Convenience aliases
alias cs='claude_session'
alias claude-session='claude_session'
# Helper functions for session management
claude_session_list() {
claude_session --list
}
claude_session_attach() {
if [[ -n "$1" ]]; then
claude_session --attach "$1"
else
echo "Usage: claude_session_attach <number>"
echo "Use 'cs -l' to see numbered session list"
fi
}
claude_session_kill() {
if [[ -n "$1" ]]; then
claude_session --kill "$1"
else
echo "Usage: claude_session_kill <number>"
echo "Use 'cs -l' to see numbered session list"
fi
}
claude_session_kill_all() {
# Get all sessions except some known system ones
local all_sessions=$(tmux list-sessions -F "#{session_name}" 2>/dev/null)
local claude_sessions=""
# Filter for likely Claude sessions (contains underscore, dash, or "claude")
while IFS= read -r session; do
if [[ "$session" =~ (claude|_|-) && "$session" != "crashes" ]]; then
claude_sessions+="$session"$'\n'
fi
done <<< "$all_sessions"
# Remove trailing newline
claude_sessions=$(echo -n "$claude_sessions")
if [[ -n "$claude_sessions" ]]; then
echo "πŸ’€ Found likely Claude sessions:"
echo "$claude_sessions" | sed 's/^/ /'
echo ""
read -p "Kill these sessions? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
echo "$claude_sessions" | xargs -I {} tmux kill-session -t {}
echo "βœ… Sessions terminated"
else
echo "❌ Cancelled"
fi
else
echo "ℹ️ No Claude sessions to kill"
fi
}
# Additional aliases
alias cs-list='claude_session --list'
alias cs-l='claude_session --list'
alias cs-attach='claude_session_attach'
alias cs-kill-all='claude_session_kill_all'
@nazq
Copy link
Author

nazq commented Jul 2, 2025

Claude Session Manager Setup & Usage Guide (git worktree aware)

πŸš€ Quick Setup

1. Download the script

# Create the directory if it doesn't exist
mkdir -p ~/.bash_functions

# Download the script
curl -o ~/.bash_functions/claude-session.sh https://gist.githubusercontent.com/nazq/3d179988627b15119944da573c91f1ab/raw/claude-session.sh

2. Add to your .bashrc

Add this line to your ~/.bashrc or ~/.bash_profile:

# Source Claude Session Manager
[ -f ~/.bash_functions/claude-session.sh ] && source ~/.bash_functions/claude-session.sh

3. Reload your shell

source ~/.bashrc
# or just open a new terminal

πŸ“– Usage

Basic Commands

  • cs - Create a new Claude session with auto-detected name
  • cs my-project - Create a session with custom name
  • cs -a - Create session and auto-attach
  • cs -c - Create session with --continue flag
  • cs -x - Create session in dangerous mode (skip permissions, auto-accept edits)
  • cs -l - List all tmux sessions with numbers
  • cs -a 3 - Attach to session #3 from the list
  • cs -k 2 - Kill session #2

Session Naming

The script automatically names sessions based on context:

  • In a git worktree repository: REPO_BRANCH (e.g., myproject_main)
  • In a git repository: REPO (e.g., myproject)
  • Outside git: Uses current directory name
  • Custom: You can always provide your own name

Examples

# In a git repo on branch 'feature/auth'
cd ~/projects/webapp
cs
# Creates session: webapp_feature_auth

# Quick attach to existing session
cs -a

# List and attach by number
cs -l          # Shows numbered list
cs -a 2        # Attach to session #2

# Custom session name
cs backend-api -a  # Create and attach to 'backend-api'

# Continue previous session
cs -c -a       # Create with --continue and attach

# Dangerous mode (auto-accept all)
cs -x -a       # Create with dangerous mode and attach

Helper Aliases

  • cs-list or cs-l - List sessions
  • cs-attach <number> - Attach to numbered session
  • cs-kill-all - Kill all Claude sessions (with confirmation)

🎯 Features

  • Auto-detects git repositories and branches
  • Sanitizes session names for tmux compatibility
  • Handles existing sessions gracefully
  • Numbered session management for easy access
  • Works with git worktrees
  • Starts Claude Code automatically in each session
  • Continue mode support for resuming previous conversations
  • Dangerous mode for trusted environments (skips permission prompts)

⚠️ Dangerous Mode

The -x or --dangerous flag runs Claude with:

  • --dangerously-skip-permissions - Skips all permission prompts
  • --permission-mode acceptEdits - Auto-accepts all edits

Use only in trusted environments! This mode gives Claude full access without confirmations.

πŸ“‹ Requirements

  • tmux
  • Claude Code CLI (claude command)
  • bash 4.0+

πŸ’‘ Tips

  • Use cs -l frequently to see all your sessions
  • The numbered list makes it easy to jump between projects
  • Sessions persist even if you disconnect
  • Each session maintains its own Claude context
  • Combine flags: cs -x -c -a for dangerous + continue + attach

@elijahr
Copy link

elijahr commented Sep 6, 2025

This is great! Thank you. I've forked it and made a version for zellij users.

https://gist.github.com/elijahr/96b5c31e98a721326ad64735334186b1

@nazq
Copy link
Author

nazq commented Sep 7, 2025

Looks great. I've been toying with switching to zellij for a while now but didn't want to do this leg work. Thanks for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment