mirror of
https://github.com/coder/registry.git
synced 2026-06-02 20:48:14 +00:00
fix: session resumption fix, and bug fixes for arg path logic (#522)
## Description Fix issue with commands being injected through prompt. Bug fix for logic in arg paths. <!-- Briefly describe what this PR does and why --> ## Type of Change - [ ] New module - [ ] New template - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder/modules/claude-code` **New version:** `v4.0.1` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun fmt`) - [ ] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> --------- Co-authored-by: DevelopmentCats <christofer@coder.com> Co-authored-by: DevelopmentCats <chris@dualriver.com>
This commit is contained in:
@@ -13,7 +13,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 = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||||
@@ -70,7 +70,7 @@ data "coder_parameter" "ai_prompt" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
|
|||||||
```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 = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder"
|
workdir = "/home/coder"
|
||||||
install_claude_code = true
|
install_claude_code = true
|
||||||
@@ -129,7 +129,7 @@ variable "claude_code_oauth_token" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
claude_code_oauth_token = var.claude_code_oauth_token
|
claude_code_oauth_token = var.claude_code_oauth_token
|
||||||
@@ -202,7 +202,7 @@ resource "coder_env" "bedrock_api_key" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||||
@@ -259,7 +259,7 @@ resource "coder_env" "google_application_credentials" {
|
|||||||
|
|
||||||
module "claude-code" {
|
module "claude-code" {
|
||||||
source = "registry.coder.com/coder/claude-code/coder"
|
source = "registry.coder.com/coder/claude-code/coder"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
agent_id = coder_agent.example.id
|
agent_id = coder_agent.example.id
|
||||||
workdir = "/home/coder/project"
|
workdir = "/home/coder/project"
|
||||||
model = "claude-sonnet-4@20250514"
|
model = "claude-sonnet-4@20250514"
|
||||||
|
|||||||
@@ -198,15 +198,16 @@ describe("claude-code", async () => {
|
|||||||
expect(startLog.stdout).toContain(`--model ${model}`);
|
expect(startLog.stdout).toContain(`--model ${model}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("claude-continue-resume-existing-session", async () => {
|
test("claude-continue-resume-task-session", async () => {
|
||||||
const { id } = await setup({
|
const { id } = await setup({
|
||||||
moduleVariables: {
|
moduleVariables: {
|
||||||
continue: "true",
|
continue: "true",
|
||||||
|
report_tasks: "true",
|
||||||
ai_prompt: "test prompt",
|
ai_prompt: "test prompt",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a mock session file with the predefined task session ID
|
// Create a mock task session file with the hardcoded task session ID
|
||||||
const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2";
|
const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2";
|
||||||
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
|
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
|
||||||
await execContainer(id, ["mkdir", "-p", sessionDir]);
|
await execContainer(id, ["mkdir", "-p", sessionDir]);
|
||||||
@@ -226,6 +227,43 @@ describe("claude-code", async () => {
|
|||||||
expect(startLog.stdout).toContain("--resume");
|
expect(startLog.stdout).toContain("--resume");
|
||||||
expect(startLog.stdout).toContain(taskSessionId);
|
expect(startLog.stdout).toContain(taskSessionId);
|
||||||
expect(startLog.stdout).toContain("Resuming existing task session");
|
expect(startLog.stdout).toContain("Resuming existing task session");
|
||||||
|
expect(startLog.stdout).toContain("--dangerously-skip-permissions");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("claude-continue-resume-standalone-session", async () => {
|
||||||
|
const { id } = await setup({
|
||||||
|
moduleVariables: {
|
||||||
|
continue: "true",
|
||||||
|
report_tasks: "false",
|
||||||
|
ai_prompt: "test prompt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionId = "some-random-session-id";
|
||||||
|
const workdir = "/home/coder/project";
|
||||||
|
const claudeJson = {
|
||||||
|
projects: {
|
||||||
|
[workdir]: {
|
||||||
|
lastSessionId: sessionId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await execContainer(id, [
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
`echo '${JSON.stringify(claudeJson)}' > /home/coder/.claude.json`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await execModuleScript(id);
|
||||||
|
|
||||||
|
const startLog = await execContainer(id, [
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
"cat /home/coder/.claude-module/agentapi-start.log",
|
||||||
|
]);
|
||||||
|
expect(startLog.stdout).toContain("--continue");
|
||||||
|
expect(startLog.stdout).toContain("Resuming existing session");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("pre-post-install-scripts", async () => {
|
test("pre-post-install-scripts", async () => {
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ module "agentapi" {
|
|||||||
ARG_PERMISSION_MODE='${var.permission_mode}' \
|
ARG_PERMISSION_MODE='${var.permission_mode}' \
|
||||||
ARG_WORKDIR='${local.workdir}' \
|
ARG_WORKDIR='${local.workdir}' \
|
||||||
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
|
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
|
||||||
|
ARG_REPORT_TASKS='${var.report_tasks}' \
|
||||||
ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
|
ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
|
||||||
ARG_BOUNDARY_VERSION='${var.boundary_version}' \
|
ARG_BOUNDARY_VERSION='${var.boundary_version}' \
|
||||||
ARG_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \
|
ARG_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ run "test_claude_code_with_custom_options" {
|
|||||||
group = "development"
|
group = "development"
|
||||||
icon = "/icon/custom.svg"
|
icon = "/icon/custom.svg"
|
||||||
model = "opus"
|
model = "opus"
|
||||||
task_prompt = "Help me write better code"
|
ai_prompt = "Help me write better code"
|
||||||
permission_mode = "plan"
|
permission_mode = "plan"
|
||||||
continue = true
|
continue = true
|
||||||
install_claude_code = false
|
install_claude_code = false
|
||||||
@@ -88,8 +88,8 @@ run "test_claude_code_with_custom_options" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
condition = var.task_prompt == "Help me write better code"
|
condition = var.ai_prompt == "Help me write better code"
|
||||||
error_message = "Task prompt variable should be set correctly"
|
error_message = "AI prompt variable should be set correctly"
|
||||||
}
|
}
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
|
|||||||
@@ -26,15 +26,19 @@ echo ".claude.json path $claude_json_path"
|
|||||||
# Check if .claude.json exists
|
# Check if .claude.json exists
|
||||||
if [ ! -f "$claude_json_path" ]; then
|
if [ ! -f "$claude_json_path" ]; then
|
||||||
echo "No .claude.json file found"
|
echo "No .claude.json file found"
|
||||||
exit 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use jq to check if lastSessionId exists for the working directory and remove it
|
# Use jq to check if lastSessionId exists for the working directory and remove it
|
||||||
|
|
||||||
if jq -e ".projects[\"$working_dir\"].lastSessionId" "$claude_json_path" > /dev/null 2>&1; then
|
if jq -e ".projects[\"$working_dir\"].lastSessionId" "$claude_json_path" > /dev/null 2>&1; then
|
||||||
# Remove lastSessionId and update the file
|
# Remove lastSessionId and update the file
|
||||||
jq "del(.projects[\"$working_dir\"].lastSessionId)" "$claude_json_path" > "${claude_json_path}.tmp" && mv "${claude_json_path}.tmp" "$claude_json_path"
|
if jq "del(.projects[\"$working_dir\"].lastSessionId)" "$claude_json_path" > "${claude_json_path}.tmp" && mv "${claude_json_path}.tmp" "$claude_json_path"; then
|
||||||
echo "Removed lastSessionId from .claude.json"
|
echo "Removed lastSessionId from .claude.json"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Failed to remove lastSessionId from .claude.json"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "No lastSessionId found in .claude.json - nothing to do"
|
echo "No lastSessionId found in .claude.json - nothing to do"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
|
|||||||
ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-}
|
ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-}
|
||||||
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
||||||
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d)
|
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d)
|
||||||
|
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
|
||||||
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
|
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
|
||||||
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
|
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
|
||||||
ARG_BOUNDARY_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"}
|
ARG_BOUNDARY_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"}
|
||||||
@@ -38,6 +39,7 @@ printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIO
|
|||||||
printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE"
|
printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE"
|
||||||
printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT"
|
printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT"
|
||||||
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
||||||
|
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
|
||||||
printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
|
printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
|
||||||
printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
|
printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
|
||||||
printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
|
printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
|
||||||
@@ -47,10 +49,18 @@ printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"
|
|||||||
|
|
||||||
echo "--------------------------------"
|
echo "--------------------------------"
|
||||||
|
|
||||||
# see the remove-last-session-id.sh script for details
|
# Clean up stale session data (see remove-last-session-id.sh for details)
|
||||||
# about why we need it
|
CAN_CONTINUE_CONVERSATION=false
|
||||||
# avoid exiting if the script fails
|
set +e
|
||||||
bash "/tmp/remove-last-session-id.sh" "$(pwd)" 2> /dev/null || true
|
bash "/tmp/remove-last-session-id.sh" "$(pwd)" 2> /dev/null
|
||||||
|
session_cleanup_exit_code=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case $session_cleanup_exit_code in
|
||||||
|
0)
|
||||||
|
CAN_CONTINUE_CONVERSATION=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
function install_boundary() {
|
function install_boundary() {
|
||||||
# Install boundary from public github repo
|
# Install boundary from public github repo
|
||||||
@@ -69,10 +79,15 @@ function validate_claude_installation() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Hardcoded task session ID for Coder task reporting
|
||||||
|
# This ensures all task sessions use a consistent, predictable ID
|
||||||
TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2"
|
TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2"
|
||||||
|
|
||||||
task_session_exists() {
|
task_session_exists() {
|
||||||
if find "$HOME/.claude" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then
|
local workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/' '-')
|
||||||
|
local project_dir="$HOME/.claude/projects/${workdir_normalized}"
|
||||||
|
|
||||||
|
if [ -d "$project_dir" ] && find "$project_dir" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
@@ -97,39 +112,63 @@ function start_agentapi() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
|
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
|
||||||
echo "Using explicit resume_session_id: $ARG_RESUME_SESSION_ID"
|
echo "Resuming task session by ID: $ARG_RESUME_SESSION_ID"
|
||||||
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
|
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
|
||||||
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions)
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
fi
|
fi
|
||||||
elif [ "$ARG_CONTINUE" = "true" ]; then
|
elif [ "$ARG_CONTINUE" = "true" ]; then
|
||||||
if task_session_exists; then
|
if [ "$ARG_REPORT_TASKS" = "true" ] && task_session_exists; then
|
||||||
echo "Task session detected (ID: $TASK_SESSION_ID)"
|
echo "Task session detected (ID: $TASK_SESSION_ID)"
|
||||||
ARGS+=(--resume "$TASK_SESSION_ID")
|
ARGS+=(--resume "$TASK_SESSION_ID")
|
||||||
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
|
echo "Resuming existing task session"
|
||||||
|
elif [ "$ARG_REPORT_TASKS" = "false" ] && [ "$CAN_CONTINUE_CONVERSATION" = true ]; then
|
||||||
|
echo "Previous session exists"
|
||||||
|
ARGS+=(--continue)
|
||||||
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions)
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
fi
|
fi
|
||||||
echo "Resuming existing task session"
|
echo "Resuming existing session"
|
||||||
else
|
else
|
||||||
echo "No existing task session found"
|
echo "No existing session found"
|
||||||
ARGS+=(--session-id "$TASK_SESSION_ID")
|
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||||
|
ARGS+=(--session-id "$TASK_SESSION_ID")
|
||||||
|
fi
|
||||||
if [ -n "$ARG_AI_PROMPT" ]; then
|
if [ -n "$ARG_AI_PROMPT" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT")
|
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||||
echo "Starting new task session with prompt"
|
ARGS+=(--dangerously-skip-permissions -- "$ARG_AI_PROMPT")
|
||||||
|
else
|
||||||
|
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
|
fi
|
||||||
|
ARGS+=(-- "$ARG_AI_PROMPT")
|
||||||
|
fi
|
||||||
|
echo "Starting new session with prompt"
|
||||||
else
|
else
|
||||||
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
if [ "$ARG_REPORT_TASKS" = "true" ] || [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions)
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
fi
|
fi
|
||||||
echo "Starting new task session"
|
echo "Starting new session"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Continue disabled, starting fresh session"
|
echo "Continue disabled, starting fresh session"
|
||||||
|
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||||
|
ARGS+=(--session-id "$TASK_SESSION_ID")
|
||||||
|
fi
|
||||||
if [ -n "$ARG_AI_PROMPT" ]; then
|
if [ -n "$ARG_AI_PROMPT" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT")
|
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||||
|
ARGS+=(--dangerously-skip-permissions -- "$ARG_AI_PROMPT")
|
||||||
|
else
|
||||||
|
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
|
fi
|
||||||
|
ARGS+=(-- "$ARG_AI_PROMPT")
|
||||||
|
fi
|
||||||
echo "Starting new session with prompt"
|
echo "Starting new session with prompt"
|
||||||
else
|
else
|
||||||
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
if [ "$ARG_REPORT_TASKS" = "true" ] || [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
|
||||||
ARGS+=(--dangerously-skip-permissions)
|
ARGS+=(--dangerously-skip-permissions)
|
||||||
fi
|
fi
|
||||||
echo "Starting claude code session"
|
echo "Starting claude code session"
|
||||||
|
|||||||
Reference in New Issue
Block a user