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)
This commit is contained in:
parent
a47ff911e1
commit
e5ccf74ccc
@ -14,7 +14,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
|
|||||||
```tf
|
```tf
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
folder = "/home/coder"
|
folder = "/home/coder"
|
||||||
install_claude_code = true
|
install_claude_code = true
|
||||||
@ -88,7 +88,7 @@ resource "coder_agent" "main" {
|
|||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
count = data.coder_workspace.me.start_count
|
count = data.coder_workspace.me.start_count
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
folder = "/home/coder"
|
folder = "/home/coder"
|
||||||
install_claude_code = true
|
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 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.
|
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
|
```tf
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
folder = "/home/coder"
|
folder = "/home/coder"
|
||||||
install_claude_code = true
|
install_claude_code = true
|
||||||
|
|||||||
@ -84,6 +84,18 @@ variable "experiment_post_install_script" {
|
|||||||
default = null
|
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 {
|
locals {
|
||||||
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
|
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) : ""
|
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
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Function to check if a command exists
|
|
||||||
command_exists() {
|
command_exists() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
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
|
if [ ! -d "${var.folder}" ]; then
|
||||||
echo "Warning: The specified folder '${var.folder}' does not exist."
|
echo "Warning: The specified folder '${var.folder}' does not exist."
|
||||||
echo "Creating the folder..."
|
echo "Creating the folder..."
|
||||||
@ -112,8 +140,6 @@ resource "coder_script" "claude_code" {
|
|||||||
mkdir -p "${var.folder}"
|
mkdir -p "${var.folder}"
|
||||||
echo "Folder created successfully."
|
echo "Folder created successfully."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run pre-install script if provided
|
|
||||||
if [ -n "${local.encoded_pre_install_script}" ]; then
|
if [ -n "${local.encoded_pre_install_script}" ]; then
|
||||||
echo "Running pre-install script..."
|
echo "Running pre-install script..."
|
||||||
echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh
|
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
|
/tmp/pre_install.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install Claude Code if enabled
|
|
||||||
if [ "${var.install_claude_code}" = "true" ]; then
|
if [ "${var.install_claude_code}" = "true" ]; then
|
||||||
if ! command_exists npm; then
|
if ! command_exists npm; then
|
||||||
echo "Error: npm is not installed. Please install Node.js and npm first."
|
echo "npm not found, checking for Node.js installation..."
|
||||||
exit 1
|
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
|
fi
|
||||||
echo "Installing Claude Code..."
|
echo "Installing Claude Code..."
|
||||||
npm install -g @anthropic-ai/claude-code@${var.claude_code_version}
|
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}
|
coder exp mcp configure claude-code ${var.folder}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run post-install script if provided
|
|
||||||
if [ -n "${local.encoded_post_install_script}" ]; then
|
if [ -n "${local.encoded_post_install_script}" ]; then
|
||||||
echo "Running post-install script..."
|
echo "Running post-install script..."
|
||||||
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
|
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
|
/tmp/post_install.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Handle terminal multiplexer selection (tmux or screen)
|
|
||||||
if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then
|
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 "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously."
|
||||||
echo "Please set only one of them to true."
|
echo "Please set only one of them to true."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run with tmux if enabled
|
if [ "${var.experiment_tmux_session_persistence}" = "true" ] && [ "${var.experiment_use_tmux}" != "true" ]; then
|
||||||
if [ "${var.experiment_use_tmux}" = "true" ]; then
|
echo "Error: Session persistence requires tmux to be enabled."
|
||||||
echo "Running Claude Code in the background with tmux..."
|
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
|
if ! command_exists tmux; then
|
||||||
echo "Error: tmux is not installed. Please install tmux manually."
|
install_tmux
|
||||||
exit 1
|
|
||||||
fi
|
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 LANG=en_US.UTF-8
|
||||||
export LC_ALL=en_US.UTF-8
|
export LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
# Create a new tmux session in detached mode
|
if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then
|
||||||
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
|
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
|
fi
|
||||||
|
|
||||||
# Run with screen if enabled
|
|
||||||
if [ "${var.experiment_use_screen}" = "true" ]; then
|
if [ "${var.experiment_use_screen}" = "true" ]; then
|
||||||
echo "Running Claude Code in the background..."
|
echo "Running Claude Code in the background..."
|
||||||
|
|
||||||
# Check if screen is installed
|
|
||||||
if ! command_exists screen; then
|
if ! command_exists screen; then
|
||||||
echo "Error: screen is not installed. Please install screen manually."
|
echo "Error: screen is not installed. Please install screen manually."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
touch "$HOME/.claude-code.log"
|
touch "$HOME/.claude-code.log"
|
||||||
|
|
||||||
# Ensure the screenrc exists
|
|
||||||
if [ ! -f "$HOME/.screenrc" ]; then
|
if [ ! -f "$HOME/.screenrc" ]; then
|
||||||
echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.claude-code.log"
|
echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.claude-code.log"
|
||||||
echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc"
|
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 "Adding 'acladd $(whoami)' to ~/.screenrc..." | tee -a "$HOME/.claude-code.log"
|
||||||
echo "acladd $(whoami)" >> "$HOME/.screenrc"
|
echo "acladd $(whoami)" >> "$HOME/.screenrc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LANG=en_US.UTF-8
|
export LANG=en_US.UTF-8
|
||||||
export LC_ALL=en_US.UTF-8
|
export LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
@ -207,7 +303,6 @@ resource "coder_script" "claude_code" {
|
|||||||
exec bash
|
exec bash
|
||||||
'
|
'
|
||||||
else
|
else
|
||||||
# Check if claude is installed before running
|
|
||||||
if ! command_exists claude; then
|
if ! command_exists claude; then
|
||||||
echo "Error: Claude Code is not installed. Please enable install_claude_code or install it manually."
|
echo "Error: Claude Code is not installed. Please enable install_claude_code or install it manually."
|
||||||
exit 1
|
exit 1
|
||||||
@ -231,6 +326,10 @@ resource "coder_app" "claude_code" {
|
|||||||
if [ "${var.experiment_use_tmux}" = "true" ]; then
|
if [ "${var.experiment_use_tmux}" = "true" ]; then
|
||||||
if tmux has-session -t claude-code 2>/dev/null; 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"
|
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
|
tmux attach-session -t claude-code
|
||||||
else
|
else
|
||||||
echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
|
echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user