## Summary - Add `auto` as a valid `permission_mode` for the claude-code module, passing `--enable-auto-mode` to the CLI when selected - Fix bypass permissions TOS prompt appearing interactively by pre-seeding `bypassPermissionsModeAccepted` in `~/.claude.json` during install (workaround for https://github.com/anthropics/claude-code/issues/25503) - Bump version `4.8.2` → `4.9.0` ## Test plan - [x] All 19 terraform tests pass (`terraform test -verbose`) - [x] Added `test_claude_code_auto_permission_mode` tftest - [x] Added `claude-auto-permission-mode` TypeScript test verifying both `--permission-mode auto` and `--enable-auto-mode` are passed - [ ] Container test with auto mode (requires Linux/Colima) - [ ] Verify bypass permissions TOS prompt no longer appears on task startup 🤖 Generated with Claude Code using Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: DevCats <christofer@coder.com>
266 lines
8.8 KiB
Bash
266 lines
8.8 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
BOLD='\033[0;1m'
|
|
|
|
command_exists() {
|
|
command -v "$1" > /dev/null 2>&1
|
|
}
|
|
|
|
ARG_CLAUDE_CODE_VERSION=${ARG_CLAUDE_CODE_VERSION:-}
|
|
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
|
ARG_INSTALL_CLAUDE_CODE=${ARG_INSTALL_CLAUDE_CODE:-}
|
|
ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"}
|
|
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH/#\~/$HOME}"
|
|
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}"
|
|
ARG_INSTALL_VIA_NPM=${ARG_INSTALL_VIA_NPM:-false}
|
|
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
|
|
ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
|
|
ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d)
|
|
ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n "${ARG_MCP_CONFIG_REMOTE_PATH:-}" | base64 -d)
|
|
ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
|
|
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
|
|
ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false}
|
|
ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-}
|
|
|
|
export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH"
|
|
|
|
echo "--------------------------------"
|
|
|
|
printf "ARG_CLAUDE_CODE_VERSION: %s\n" "$ARG_CLAUDE_CODE_VERSION"
|
|
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
|
printf "ARG_INSTALL_CLAUDE_CODE: %s\n" "$ARG_INSTALL_CLAUDE_CODE"
|
|
printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$ARG_CLAUDE_BINARY_PATH"
|
|
printf "ARG_INSTALL_VIA_NPM: %s\n" "$ARG_INSTALL_VIA_NPM"
|
|
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
|
|
printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
|
|
printf "ARG_MCP: %s\n" "$ARG_MCP"
|
|
printf "ARG_MCP_CONFIG_REMOTE_PATH: %s\n" "$ARG_MCP_CONFIG_REMOTE_PATH"
|
|
printf "ARG_ALLOWED_TOOLS: %s\n" "$ARG_ALLOWED_TOOLS"
|
|
printf "ARG_DISALLOWED_TOOLS: %s\n" "$ARG_DISALLOWED_TOOLS"
|
|
printf "ARG_ENABLE_AIBRIDGE: %s\n" "$ARG_ENABLE_AIBRIDGE"
|
|
|
|
echo "--------------------------------"
|
|
|
|
function add_mcp_servers() {
|
|
local mcp_json="$1"
|
|
local source_desc="$2"
|
|
|
|
while IFS= read -r server_name && IFS= read -r server_json; do
|
|
echo "------------------------"
|
|
echo "Executing: claude mcp add-json \"$server_name\" '$server_json' ($source_desc)"
|
|
claude mcp add-json "$server_name" "$server_json" || echo "Warning: Failed to add MCP server '$server_name', continuing..."
|
|
echo "------------------------"
|
|
echo ""
|
|
done < <(echo "$mcp_json" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
|
|
}
|
|
|
|
function add_path_to_shell_profiles() {
|
|
local path_dir="$1"
|
|
|
|
for profile in "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.zshrc"; do
|
|
if [ -f "$profile" ]; then
|
|
if ! grep -q "$path_dir" "$profile" 2> /dev/null; then
|
|
echo "export PATH=\"\$PATH:$path_dir\"" >> "$profile"
|
|
echo "Added $path_dir to $profile"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
local fish_config="$HOME/.config/fish/config.fish"
|
|
if [ -f "$fish_config" ]; then
|
|
if ! grep -q "$path_dir" "$fish_config" 2> /dev/null; then
|
|
echo "fish_add_path $path_dir" >> "$fish_config"
|
|
echo "Added $path_dir to $fish_config"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function ensure_claude_in_path() {
|
|
local CLAUDE_BIN=""
|
|
if command -v claude > /dev/null 2>&1; then
|
|
CLAUDE_BIN=$(command -v claude)
|
|
elif [ -x "$ARG_CLAUDE_BINARY_PATH/claude" ]; then
|
|
CLAUDE_BIN="$ARG_CLAUDE_BINARY_PATH/claude"
|
|
elif [ -x "$HOME/.local/bin/claude" ]; then
|
|
CLAUDE_BIN="$HOME/.local/bin/claude"
|
|
fi
|
|
|
|
if [ -z "$CLAUDE_BIN" ] || [ ! -x "$CLAUDE_BIN" ]; then
|
|
echo "Warning: Could not find claude binary"
|
|
return
|
|
fi
|
|
|
|
local CLAUDE_DIR
|
|
CLAUDE_DIR=$(dirname "$CLAUDE_BIN")
|
|
|
|
if [ -n "${CODER_SCRIPT_BIN_DIR:-}" ] && [ ! -e "$CODER_SCRIPT_BIN_DIR/claude" ]; then
|
|
ln -s "$CLAUDE_BIN" "$CODER_SCRIPT_BIN_DIR/claude"
|
|
echo "Created symlink: $CODER_SCRIPT_BIN_DIR/claude -> $CLAUDE_BIN"
|
|
fi
|
|
|
|
add_path_to_shell_profiles "$CLAUDE_DIR"
|
|
}
|
|
|
|
function install_claude_code_cli() {
|
|
if [ "$ARG_INSTALL_CLAUDE_CODE" != "true" ]; then
|
|
echo "Skipping Claude Code installation as per configuration."
|
|
ensure_claude_in_path
|
|
return
|
|
fi
|
|
|
|
# Use npm when install_via_npm is true
|
|
if [ "$ARG_INSTALL_VIA_NPM" = "true" ]; then
|
|
echo "WARNING: npm installation method will be deprecated and removed in the next major release."
|
|
echo "Installing Claude Code via npm (version: $ARG_CLAUDE_CODE_VERSION)"
|
|
npm install -g "@anthropic-ai/claude-code@$ARG_CLAUDE_CODE_VERSION"
|
|
echo "Installed Claude Code via npm. Version: $(claude --version || echo 'unknown')"
|
|
else
|
|
echo "Installing Claude Code via official installer"
|
|
set +e
|
|
curl -fsSL claude.ai/install.sh | bash -s -- "$ARG_CLAUDE_CODE_VERSION" 2>&1
|
|
CURL_EXIT=${PIPESTATUS[0]}
|
|
set -e
|
|
if [ $CURL_EXIT -ne 0 ]; then
|
|
echo "Claude Code installer failed with exit code $CURL_EXIT"
|
|
fi
|
|
echo "Installed Claude Code successfully. Version: $(claude --version || echo 'unknown')"
|
|
fi
|
|
|
|
ensure_claude_in_path
|
|
}
|
|
|
|
function setup_claude_configurations() {
|
|
if [ ! -d "$ARG_WORKDIR" ]; then
|
|
echo "Warning: The specified folder '$ARG_WORKDIR' does not exist."
|
|
echo "Creating the folder..."
|
|
mkdir -p "$ARG_WORKDIR"
|
|
echo "Folder created successfully."
|
|
fi
|
|
|
|
module_path="$HOME/.claude-module"
|
|
mkdir -p "$module_path"
|
|
|
|
if [ "$ARG_MCP" != "" ]; then
|
|
(
|
|
cd "$ARG_WORKDIR"
|
|
add_mcp_servers "$ARG_MCP" "in $ARG_WORKDIR"
|
|
)
|
|
fi
|
|
|
|
if [ -n "$ARG_MCP_CONFIG_REMOTE_PATH" ] && [ "$ARG_MCP_CONFIG_REMOTE_PATH" != "[]" ]; then
|
|
(
|
|
cd "$ARG_WORKDIR"
|
|
for url in $(echo "$ARG_MCP_CONFIG_REMOTE_PATH" | jq -r '.[]'); do
|
|
echo "Fetching MCP configuration from: $url"
|
|
mcp_json=$(curl -fsSL "$url") || {
|
|
echo "Warning: Failed to fetch MCP configuration from '$url', continuing..."
|
|
continue
|
|
}
|
|
if ! echo "$mcp_json" | jq -e '.mcpServers' > /dev/null 2>&1; then
|
|
echo "Warning: Invalid MCP configuration from '$url' (missing mcpServers), continuing..."
|
|
continue
|
|
fi
|
|
add_mcp_servers "$mcp_json" "from $url"
|
|
done
|
|
)
|
|
fi
|
|
|
|
if [ -n "$ARG_ALLOWED_TOOLS" ]; then
|
|
coder --allowedTools "$ARG_ALLOWED_TOOLS"
|
|
fi
|
|
|
|
if [ -n "$ARG_DISALLOWED_TOOLS" ]; then
|
|
coder --disallowedTools "$ARG_DISALLOWED_TOOLS"
|
|
fi
|
|
|
|
}
|
|
|
|
function configure_standalone_mode() {
|
|
echo "Configuring Claude Code for standalone mode..."
|
|
|
|
if [ -z "${CLAUDE_API_KEY:-}" ] && [ "$ARG_ENABLE_AIBRIDGE" = "false" ]; then
|
|
echo "Note: Neither claude_api_key nor enable_aibridge is set, skipping authentication setup"
|
|
return
|
|
fi
|
|
|
|
local claude_config="$HOME/.claude.json"
|
|
local workdir_normalized
|
|
workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/' '-')
|
|
|
|
# Create or update .claude.json with minimal configuration for API key auth
|
|
# This skips the interactive login prompt and onboarding screens
|
|
if [ -f "$claude_config" ]; then
|
|
echo "Updating existing Claude configuration at $claude_config"
|
|
|
|
jq --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \
|
|
'.autoUpdaterStatus = "disabled" |
|
|
.autoModeAccepted = true |
|
|
.bypassPermissionsModeAccepted = true |
|
|
.hasAcknowledgedCostThreshold = true |
|
|
.hasCompletedOnboarding = true |
|
|
.primaryApiKey = $apikey |
|
|
.projects[$workdir].hasCompletedProjectOnboarding = true |
|
|
.projects[$workdir].hasTrustDialogAccepted = true' \
|
|
"$claude_config" > "${claude_config}.tmp" && mv "${claude_config}.tmp" "$claude_config"
|
|
else
|
|
echo "Creating new Claude configuration at $claude_config"
|
|
cat > "$claude_config" << EOF
|
|
{
|
|
"autoUpdaterStatus": "disabled",
|
|
"autoModeAccepted": true,
|
|
"bypassPermissionsModeAccepted": true,
|
|
"hasAcknowledgedCostThreshold": true,
|
|
"hasCompletedOnboarding": true,
|
|
"primaryApiKey": "${CLAUDE_API_KEY:-}",
|
|
"projects": {
|
|
"$ARG_WORKDIR": {
|
|
"hasCompletedProjectOnboarding": true,
|
|
"hasTrustDialogAccepted": true
|
|
}
|
|
}
|
|
}
|
|
EOF
|
|
fi
|
|
|
|
echo "Standalone mode configured successfully"
|
|
}
|
|
|
|
function report_tasks() {
|
|
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
|
echo "Configuring Claude Code to report tasks via Coder MCP..."
|
|
export CODER_MCP_APP_STATUS_SLUG="$ARG_MCP_APP_STATUS_SLUG"
|
|
export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
|
|
coder exp mcp configure claude-code "$ARG_WORKDIR"
|
|
else
|
|
configure_standalone_mode
|
|
fi
|
|
}
|
|
|
|
function accept_auto_mode() {
|
|
# Pre-accept the auto mode TOS prompt so it doesn't appear interactively.
|
|
# Claude Code shows a confirmation dialog for auto mode that blocks
|
|
# non-interactive/headless usage.
|
|
# Note: bypassPermissions acceptance is already handled by
|
|
# coder exp mcp configure (task mode) and configure_standalone_mode.
|
|
local claude_config="$HOME/.claude.json"
|
|
|
|
if [ -f "$claude_config" ]; then
|
|
jq '.autoModeAccepted = true' \
|
|
"$claude_config" > "${claude_config}.tmp" && mv "${claude_config}.tmp" "$claude_config"
|
|
else
|
|
echo '{"autoModeAccepted": true}' > "$claude_config"
|
|
fi
|
|
|
|
echo "Pre-accepted auto mode prompt"
|
|
}
|
|
|
|
install_claude_code_cli
|
|
setup_claude_configurations
|
|
report_tasks
|
|
|
|
if [ "$ARG_PERMISSION_MODE" = "auto" ]; then
|
|
accept_auto_mode
|
|
fi
|