Skip to content

Instantly share code, notes, and snippets.

@nazq
Last active January 22, 2026 19:40
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
# Helper function to parse combined short flags
_parse_short_flags() {
local flags="$1"
while [[ -n "$flags" ]]; do
local flag="${flags:0:1}"
flags="${flags:1}"
case "$flag" in
a) auto_attach=true ;;
c) continue_mode=true ;;
x) dangerous_mode=true ;;
l) list_sessions=true ;;
h)
echo "Usage: claude_session [session_name] [options]"
echo ""
echo "Creates a tmux session with Claude Code running"
echo ""
echo "Options:"
echo " session_name Custom session name (optional)"
echo " -a, --attach Automatically attach to session after creation"
echo " -a NUM Attach to session number NUM from list"
echo " -c, --continue Start Claude with --continue flag"
echo " -x, --dangerous Start Claude with --dangerously-skip-permissions"
echo " -l, --list List all active tmux sessions with numbers"
echo " -k NUM, --kill NUM Kill session number NUM from list"
echo " -h, --help Show this help message"
echo ""
echo "Combined flags: -acx is equivalent to -a -c -x"
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 -ca # Create session with --continue and attach"
echo " cs -acx # Attach + continue + dangerous mode"
echo " cs -xa my-proj # Dangerous mode + attach + custom name"
return 0
;;
*)
echo "❌ Unknown flag: -$flag"
echo "Use 'cs -h' for help"
return 1
;;
esac
done
return 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--list)
list_sessions=true
shift
;;
--kill)
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)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
attach_number="$2"
shift 2
else
auto_attach=true
shift
fi
;;
--continue)
continue_mode=true
shift
;;
--dangerous)
dangerous_mode=true
shift
;;
--help)
_parse_short_flags "h"
return 0
;;
-k)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
kill_number="$2"
shift 2
else
echo "❌ -k requires a session number"
echo "Use 'cs -l' to see numbered sessions"
return 1
fi
;;
-[aclxh]*)
# Handle combined short flags like -acx, -xa, -cal, etc.
local flags="${1#-}"
shift
# Check if next arg is a number (for -a NUM case)
if [[ "$flags" == "a" && -n "$1" && "$1" =~ ^[0-9]+$ ]]; then
attach_number="$1"
shift
else
_parse_short_flags "$flags" || return 1
fi
;;
-*)
echo "❌ Unknown option: $1"
echo "Use 'cs -h' for help"
return 1
;;
*)
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 --chrome"
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

@nazq
Copy link
Author

nazq commented Jan 22, 2026

Added --chrome flag to all claude invocations

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