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:
35C4n0r
2025-11-12 21:03:37 +05:30
committed by GitHub
parent 4edfdae572
commit 9e89f04691
6 changed files with 112 additions and 30 deletions
+6 -6
View File
@@ -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"