From a07a10af4fa83055dc75db8b3e769aa98f7f8d6b Mon Sep 17 00:00:00 2001 From: default Date: Sun, 25 Oct 2026 08:48:37 +0000 Subject: [PATCH 1/2] feat(codex): allow user-managed configuration --- registry/coder-labs/modules/codex/README.md | 22 ++++++ .../coder-labs/modules/codex/main.test.ts | 28 ++++++++ registry/coder-labs/modules/codex/main.tf | 12 ++++ .../coder-labs/modules/codex/main.tftest.hcl | 71 +++++++++++++++++++ .../modules/codex/scripts/install.sh.tftpl | 5 ++ 5 files changed, 138 insertions(+) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 39811f4a3..91fbcd914 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -22,6 +22,28 @@ module "codex" { > [!WARNING] > If upgrading from v4.x.x of this module: v5 is a major refactor that drops support for Coder Tasks and [Boundary](https://coder.com/docs/ai-coder/agent-firewall). Keep using v4.x.x if you depend on them. See the [PR description](https://github.com/coder/registry/pull/879) for a full migration guide. +## Existing user-managed configuration + +For persistent workspaces where users own their Codex configuration, set +`manage_config = false`. The module will not create, replace, or edit `config.toml`, +including on the first run. Installation and binary validation still run, as does +API-key authentication when explicitly configured. With no API key supplied, the +module leaves existing authentication in place. + +```tf +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.4.2" + agent_id = coder_agent.main.id + manage_config = false +} +``` + +Configuration inputs such as MCP servers, model reasoning effort, custom base TOML, +and AI Gateway require configuration management to remain enabled. A `workdir` still +creates the directory when management is disabled, but does not mark it trusted in +Codex. The default (`manage_config = true`) preserves the existing module behavior. + ## Examples ### Standalone mode with a launcher app diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index a0de25c22..cb48a0ce7 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -258,6 +258,34 @@ describe("codex", async () => { expect(installLog).toContain("Skipping Codex installation"); }); + test("user-managed config remains unchanged across repeated installs", async () => { + const { id, scripts } = await setup({ + moduleVariables: { manage_config: "false" }, + }); + const configPath = "/home/coder/.codex/config.toml"; + const original = '# My provider and preferences\nmodel = "custom"\n'; + await execContainer(id, ["mkdir", "-p", "/home/coder/.codex"]); + await writeFileContainer(id, configPath, original); + await runScripts(id, scripts); + await runScripts(id, scripts); + expect(await readFileContainer(id, configPath)).toBe(original); + expect(await installLog(id)).toContain("Skipping Codex configuration"); + }); + + test("user-managed config does not create a config directory", async () => { + const { id, scripts } = await setup({ + moduleVariables: { manage_config: "false" }, + }); + await runScripts(id, scripts); + const result = await execContainer(id, [ + "test", + "!", + "-e", + "/home/coder/.codex", + ]); + expect(result.exitCode).toBe(0); + }); + test("install-codex-version", async () => { const version = "0.134.0"; const { id, coderEnvVars, scripts } = await setup({ diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index 7674d1b21..6c1c2ed28 100644 --- a/registry/coder-labs/modules/codex/main.tf +++ b/registry/coder-labs/modules/codex/main.tf @@ -61,6 +61,17 @@ variable "openai_api_key" { default = "" } +variable "manage_config" { + description = "Whether to write the module-managed Codex config.toml block. Disable to keep an existing user-managed configuration untouched. CLI installation and explicit API-key authentication still run." + type = bool + default = true + + validation { + condition = var.manage_config || (var.base_config_toml == "" && var.mcp == "" && length(var.mcp_config_remote_path) == 0 && var.model_reasoning_effort == "" && !var.enable_ai_gateway) + error_message = "manage_config must be true when setting base_config_toml, mcp, mcp_config_remote_path, model_reasoning_effort, or enable_ai_gateway." + } +} + variable "base_config_toml" { type = string description = <<-EOT @@ -143,6 +154,7 @@ locals { EOF install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { ARG_INSTALL = tostring(var.install_codex) + ARG_MANAGE_CONFIG = tostring(var.manage_config) ARG_CODEX_VERSION = var.codex_version ARG_WORKDIR = local.workdir != "" ? base64encode(local.workdir) : "" ARG_BASE_CONFIG_TOML = var.base_config_toml != "" ? base64encode(var.base_config_toml) : "" diff --git a/registry/coder-labs/modules/codex/main.tftest.hcl b/registry/coder-labs/modules/codex/main.tftest.hcl index e3f3c9716..e83296ee6 100644 --- a/registry/coder-labs/modules/codex/main.tftest.hcl +++ b/registry/coder-labs/modules/codex/main.tftest.hcl @@ -219,3 +219,74 @@ run "test_mcp_config_remote_path_default" { error_message = "mcp_config_remote_path should default to an empty list" } } + +run "test_user_managed_config" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + } + assert { + condition = strcontains(local.install_script, "ARG_MANAGE_CONFIG='false'") + error_message = "The install script must skip configuration when management is disabled" + } +} + +run "test_managed_config_default" { + command = plan + variables { agent_id = "test-agent" } + assert { + condition = var.manage_config + error_message = "Configuration management must remain enabled by default" + } +} + +run "test_unmanaged_rejects_base_config_toml" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + base_config_toml = "model = \"custom\"" + } + expect_failures = [var.manage_config] +} + +run "test_unmanaged_rejects_mcp" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + mcp = "[mcp_servers.example]" + } + expect_failures = [var.manage_config] +} + +run "test_unmanaged_rejects_mcp_config_remote_path" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + mcp_config_remote_path = ["https://example.com/mcp.toml"] + } + expect_failures = [var.manage_config] +} + +run "test_unmanaged_rejects_model_reasoning_effort" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + model_reasoning_effort = "high" + } + expect_failures = [var.manage_config] +} + +run "test_unmanaged_rejects_enable_ai_gateway" { + command = plan + variables { + agent_id = "test-agent" + manage_config = false + enable_ai_gateway = true + } + expect_failures = [var.manage_config] +} diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 0f81a7dc2..bfd5bfd9e 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -10,6 +10,7 @@ command_exists() { } ARG_INSTALL='${ARG_INSTALL}' +ARG_MANAGE_CONFIG='${ARG_MANAGE_CONFIG}' ARG_CODEX_VERSION='${ARG_CODEX_VERSION}' ARG_WORKDIR=$(echo -n '${ARG_WORKDIR}' | base64 -d) ARG_BASE_CONFIG_TOML=$(echo -n '${ARG_BASE_CONFIG_TOML}' | base64 -d) @@ -130,6 +131,10 @@ EOF } function populate_config_toml() { + if [ "$${ARG_MANAGE_CONFIG}" != "true" ]; then + echo "Skipping Codex configuration; config.toml remains user-managed." + return + fi local config_path="$${CODEX_HOME:-$HOME/.codex}/config.toml" mkdir -p "$(dirname "$${config_path}")" From 62cdaee0a426ef7066ba88eeef141ffca36ff473 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 25 Oct 2026 08:48:54 +0000 Subject: [PATCH 2/2] chore(codex): bump module examples to 5.5.0 --- registry/coder-labs/modules/codex/README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 91fbcd914..f3b5274d2 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -13,7 +13,7 @@ Install and configure the [Codex CLI](https://github.com/openai/codex) in your w ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key } @@ -27,13 +27,12 @@ module "codex" { For persistent workspaces where users own their Codex configuration, set `manage_config = false`. The module will not create, replace, or edit `config.toml`, including on the first run. Installation and binary validation still run, as does -API-key authentication when explicitly configured. With no API key supplied, the -module leaves existing authentication in place. +API-key authentication when explicitly configured. Existing authentication is left in place when no API key is supplied. ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id manage_config = false } @@ -55,7 +54,7 @@ locals { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id workdir = local.codex_workdir openai_api_key = var.openai_api_key @@ -86,7 +85,7 @@ resource "coder_app" "codex" { ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_ai_gateway = true @@ -110,7 +109,7 @@ When `enable_ai_gateway = true`, the module configures Codex to use the `aigatew ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" openai_api_key = var.openai_api_key @@ -156,7 +155,7 @@ The module exposes the `scripts` output: an ordered list of `coder exp sync` nam ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.4.2" + version = "5.5.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key }