diff --git a/registry/coder-labs/modules/gemini/README.md b/registry/coder-labs/modules/gemini/README.md index 57842ac6..f625a118 100644 --- a/registry/coder-labs/modules/gemini/README.md +++ b/registry/coder-labs/modules/gemini/README.md @@ -13,7 +13,7 @@ Run [Gemini CLI](https://github.com/google-gemini/gemini-cli) in your workspace ```tf module "gemini" { source = "registry.coder.com/coder-labs/gemini/coder" - version = "3.0.1" + version = "3.0.2" agent_id = coder_agent.main.id folder = "/home/coder/project" } @@ -46,7 +46,7 @@ variable "gemini_api_key" { module "gemini" { source = "registry.coder.com/coder-labs/gemini/coder" - version = "3.0.1" + version = "3.0.2" agent_id = coder_agent.main.id gemini_api_key = var.gemini_api_key folder = "/home/coder/project" @@ -94,7 +94,7 @@ data "coder_parameter" "ai_prompt" { module "gemini" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder-labs/gemini/coder" - version = "3.0.1" + version = "3.0.2" agent_id = coder_agent.main.id gemini_api_key = var.gemini_api_key gemini_model = "gemini-2.5-flash" @@ -134,7 +134,7 @@ For enterprise users who prefer Google's Vertex AI platform: ```tf module "gemini" { source = "registry.coder.com/coder-labs/gemini/coder" - version = "3.0.1" + version = "3.0.2" agent_id = coder_agent.main.id gemini_api_key = var.gemini_api_key folder = "/home/coder/project" diff --git a/registry/coder-labs/modules/gemini/main.test.ts b/registry/coder-labs/modules/gemini/main.test.ts index 0680ce35..04ed11c6 100644 --- a/registry/coder-labs/modules/gemini/main.test.ts +++ b/registry/coder-labs/modules/gemini/main.test.ts @@ -263,6 +263,34 @@ describe("gemini", async () => { expect(resp).toContain("Running automated task:"); }); + test("task-prompt-with-special-characters", async () => { + // A single quote in the prompt previously broke shell quoting in the start + // script. The prompt must be passed through verbatim and never interpreted + // by the shell. + const taskPrompt = `dummy prompt' ; touch /tmp/task-prompt-marker ; echo '`; + const { id } = await setup({ + moduleVariables: { + task_prompt: taskPrompt, + }, + }); + await execModuleScript(id); + + // No part of the prompt should be executed by the shell. + const marker = await execContainer(id, [ + "bash", + "-c", + "test -e /tmp/task-prompt-marker && echo CREATED || echo SAFE", + ]); + expect(marker.stdout).toContain("SAFE"); + + // The prompt must be passed through verbatim, including the single quotes. + const promptFile = await readFileContainer( + id, + "/home/coder/.gemini-module/prompt.txt", + ); + expect(promptFile).toContain(taskPrompt); + }); + test("start-without-prompt", async () => { const { id } = await setup(); await execModuleScript(id); diff --git a/registry/coder-labs/modules/gemini/main.tf b/registry/coder-labs/modules/gemini/main.tf index 336c112f..3dfd1774 100644 --- a/registry/coder-labs/modules/gemini/main.tf +++ b/registry/coder-labs/modules/gemini/main.tf @@ -216,7 +216,7 @@ module "agentapi" { GEMINI_YOLO_MODE='${var.enable_yolo_mode}' \ GEMINI_MODEL='${var.gemini_model}' \ GEMINI_START_DIRECTORY='${var.folder}' \ - GEMINI_TASK_PROMPT='${var.task_prompt}' \ + GEMINI_TASK_PROMPT='${base64encode(var.task_prompt)}' \ /tmp/start.sh EOT } diff --git a/registry/coder-labs/modules/gemini/scripts/start.sh b/registry/coder-labs/modules/gemini/scripts/start.sh index eed55090..d3204312 100644 --- a/registry/coder-labs/modules/gemini/scripts/start.sh +++ b/registry/coder-labs/modules/gemini/scripts/start.sh @@ -44,6 +44,13 @@ else } fi +# The task prompt is base64-encoded in main.tf so prompts containing single +# quotes or other shell metacharacters do not break the start script. Decode +# it before use. +if [ -n "${GEMINI_TASK_PROMPT:-}" ]; then + GEMINI_TASK_PROMPT=$(echo -n "$GEMINI_TASK_PROMPT" | base64 -d) +fi + if [ -n "$GEMINI_TASK_PROMPT" ]; then printf "Running automated task: %s\n" "$GEMINI_TASK_PROMPT" PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GEMINI_TASK_PROMPT"