From e5ccf74ccc246df661b40db3f02c5d7e446033a1 Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 19 Jun 2025 16:46:18 -0500 Subject: [PATCH] feat: claude-code workspace persistence (#154) ## Description Add Tmux Plugin Manager with resurrect and continuum plugins. Add functionality to be able to enable workspace persistence to save the tmux session automatically so that it can persist through workspace restarts. --- ## Type of Change - [ ] New module - [ ] Bug fix - [X] Feature/enhancement - [ ] Documentation - [ ] Other --- ## Module Information **Path:** `registry/coder/modules/claude-code` **New version:** `v1.4.0` **Breaking change:** [ ] Yes [X] No --- ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally --- ## Related Issues Closes [#29](https://github.com/coder/registry/issues/29) --- registry/coder/modules/claude-code/README.md | 29 +++- registry/coder/modules/claude-code/main.tf | 149 +++++++++++++++---- 2 files changed, 150 insertions(+), 28 deletions(-) diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 5056ce71..21ff81f2 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -14,7 +14,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "1.3.1" + version = "1.4.0" agent_id = coder_agent.example.id folder = "/home/coder" install_claude_code = true @@ -88,7 +88,7 @@ resource "coder_agent" "main" { module "claude-code" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/claude-code/coder" - version = "1.3.1" + version = "1.4.0" agent_id = coder_agent.example.id folder = "/home/coder" install_claude_code = true @@ -100,6 +100,29 @@ module "claude-code" { } ``` +## Session Persistence (Experimental) + +Enable automatic session persistence to maintain Claude Code sessions across workspace restarts: + +```tf +module "claude-code" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/claude-code/coder" + version = "1.4.0" + agent_id = coder_agent.example.id + folder = "/home/coder" + install_claude_code = true + + # Enable tmux with session persistence + experiment_use_tmux = true + experiment_tmux_session_persistence = true + experiment_tmux_session_save_interval = "10" # Save every 10 minutes + experiment_report_tasks = true +} +``` + +Session persistence automatically saves and restores your Claude Code environment, including working directory and command history. + ## Run standalone Run Claude Code as a standalone app in your workspace. This will install Claude Code and run it directly without using screen or any task reporting to the Coder UI. @@ -107,7 +130,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "1.3.1" + version = "1.4.0" agent_id = coder_agent.example.id folder = "/home/coder" install_claude_code = true diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 1435a2f3..d699b4f1 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -84,6 +84,18 @@ variable "experiment_post_install_script" { default = null } +variable "experiment_tmux_session_persistence" { + type = bool + description = "Whether to enable tmux session persistence across workspace restarts." + default = false +} + +variable "experiment_tmux_session_save_interval" { + type = string + description = "How often to save tmux sessions in minutes." + default = "15" +} + locals { encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : "" @@ -98,12 +110,28 @@ resource "coder_script" "claude_code" { #!/bin/bash set -e - # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } - # Check if the specified folder exists + install_tmux() { + echo "Installing tmux..." + if command_exists apt-get; then + sudo apt-get update && sudo apt-get install -y tmux + elif command_exists yum; then + sudo yum install -y tmux + elif command_exists dnf; then + sudo dnf install -y tmux + elif command_exists pacman; then + sudo pacman -S --noconfirm tmux + elif command_exists apk; then + sudo apk add tmux + else + echo "Error: Unable to install tmux automatically. Package manager not recognized." + exit 1 + fi + } + if [ ! -d "${var.folder}" ]; then echo "Warning: The specified folder '${var.folder}' does not exist." echo "Creating the folder..." @@ -112,8 +140,6 @@ resource "coder_script" "claude_code" { mkdir -p "${var.folder}" echo "Folder created successfully." fi - - # Run pre-install script if provided if [ -n "${local.encoded_pre_install_script}" ]; then echo "Running pre-install script..." echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh @@ -121,11 +147,30 @@ resource "coder_script" "claude_code" { /tmp/pre_install.sh fi - # Install Claude Code if enabled if [ "${var.install_claude_code}" = "true" ]; then if ! command_exists npm; then - echo "Error: npm is not installed. Please install Node.js and npm first." - exit 1 + echo "npm not found, checking for Node.js installation..." + if ! command_exists node; then + echo "Node.js not found, installing Node.js via NVM..." + export NVM_DIR="$HOME/.nvm" + if [ ! -d "$NVM_DIR" ]; then + mkdir -p "$NVM_DIR" + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + else + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + fi + + nvm install --lts + nvm use --lts + nvm alias default node + + echo "Node.js installed: $(node --version)" + echo "npm installed: $(npm --version)" + else + echo "Node.js is installed but npm is not available. Please install npm manually." + exit 1 + fi fi echo "Installing Claude Code..." npm install -g @anthropic-ai/claude-code@${var.claude_code_version} @@ -136,7 +181,6 @@ resource "coder_script" "claude_code" { coder exp mcp configure claude-code ${var.folder} fi - # Run post-install script if provided if [ -n "${local.encoded_post_install_script}" ]; then echo "Running post-install script..." echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh @@ -144,46 +188,97 @@ resource "coder_script" "claude_code" { /tmp/post_install.sh fi - # Handle terminal multiplexer selection (tmux or screen) if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously." echo "Please set only one of them to true." exit 1 fi - # Run with tmux if enabled - if [ "${var.experiment_use_tmux}" = "true" ]; then - echo "Running Claude Code in the background with tmux..." + if [ "${var.experiment_tmux_session_persistence}" = "true" ] && [ "${var.experiment_use_tmux}" != "true" ]; then + echo "Error: Session persistence requires tmux to be enabled." + echo "Please set experiment_use_tmux = true when using session persistence." + exit 1 + fi - # Check if tmux is installed + if [ "${var.experiment_use_tmux}" = "true" ]; then if ! command_exists tmux; then - echo "Error: tmux is not installed. Please install tmux manually." - exit 1 + install_tmux fi - touch "$HOME/.claude-code.log" + if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then + echo "Setting up tmux session persistence..." + if ! command_exists git; then + echo "Git not found, installing git..." + if command_exists apt-get; then + sudo apt-get update && sudo apt-get install -y git + elif command_exists yum; then + sudo yum install -y git + elif command_exists dnf; then + sudo dnf install -y git + elif command_exists pacman; then + sudo pacman -S --noconfirm git + elif command_exists apk; then + sudo apk add git + else + echo "Error: Unable to install git automatically. Package manager not recognized." + echo "Please install git manually to enable session persistence." + exit 1 + fi + fi + + mkdir -p ~/.tmux/plugins + if [ ! -d ~/.tmux/plugins/tpm ]; then + git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm + fi + + cat > ~/.tmux.conf << EOF +# Claude Code tmux persistence configuration +set -g @plugin 'tmux-plugins/tmux-resurrect' +set -g @plugin 'tmux-plugins/tmux-continuum' +# Configure session persistence +set -g @resurrect-processes ':all:' +set -g @resurrect-capture-pane-contents 'on' +set -g @resurrect-save-bash-history 'on' +set -g @continuum-restore 'on' +set -g @continuum-save-interval '${var.experiment_tmux_session_save_interval}' +set -g @continuum-boot 'on' +set -g @continuum-save-on 'on' + +# Initialize plugin manager +run '~/.tmux/plugins/tpm/tpm' +EOF + + ~/.tmux/plugins/tpm/scripts/install_plugins.sh + fi + + echo "Running Claude Code in the background with tmux..." + touch "$HOME/.claude-code.log" export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 - # Create a new tmux session in detached mode - tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\"" - + if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then + sleep 3 + + if ! tmux has-session -t claude-code 2>/dev/null; then + # Only create a new session if one doesn't exist + tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\"" + fi + else + if ! tmux has-session -t claude-code 2>/dev/null; then + tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\"" + fi + fi fi - # Run with screen if enabled if [ "${var.experiment_use_screen}" = "true" ]; then echo "Running Claude Code in the background..." - - # Check if screen is installed if ! command_exists screen; then echo "Error: screen is not installed. Please install screen manually." exit 1 fi touch "$HOME/.claude-code.log" - - # Ensure the screenrc exists if [ ! -f "$HOME/.screenrc" ]; then echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.claude-code.log" echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc" @@ -198,6 +293,7 @@ resource "coder_script" "claude_code" { echo "Adding 'acladd $(whoami)' to ~/.screenrc..." | tee -a "$HOME/.claude-code.log" echo "acladd $(whoami)" >> "$HOME/.screenrc" fi + export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 @@ -207,7 +303,6 @@ resource "coder_script" "claude_code" { exec bash ' else - # Check if claude is installed before running if ! command_exists claude; then echo "Error: Claude Code is not installed. Please enable install_claude_code or install it manually." exit 1 @@ -231,6 +326,10 @@ resource "coder_app" "claude_code" { if [ "${var.experiment_use_tmux}" = "true" ]; then if tmux has-session -t claude-code 2>/dev/null; then echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log" + # If Claude isn't running in the session, start it without the prompt + if ! tmux list-panes -t claude-code -F '#{pane_current_command}' | grep -q "claude"; then + tmux send-keys -t claude-code "cd ${var.folder} && claude -c --dangerously-skip-permissions" C-m + fi tmux attach-session -t claude-code else echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"