Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions registry/coder-labs/modules/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -22,6 +22,27 @@ 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. 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.5.0"
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
Expand All @@ -33,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
Expand Down Expand Up @@ -64,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
Expand All @@ -88,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
Expand Down Expand Up @@ -134,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
}
Expand Down
28 changes: 28 additions & 0 deletions registry/coder-labs/modules/codex/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
12 changes: 12 additions & 0 deletions registry/coder-labs/modules/codex/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) : ""
Expand Down
71 changes: 71 additions & 0 deletions registry/coder-labs/modules/codex/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
5 changes: 5 additions & 0 deletions registry/coder-labs/modules/codex/scripts/install.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}")"

Expand Down