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
14 changes: 8 additions & 6 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.1"
version = "5.4.2"
agent_id = coder_agent.main.id
openai_api_key = var.openai_api_key
}
Expand All @@ -33,7 +33,7 @@ locals {

module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "5.4.1"
version = "5.4.2"
agent_id = coder_agent.main.id
workdir = local.codex_workdir
openai_api_key = var.openai_api_key
Expand Down Expand Up @@ -64,7 +64,7 @@ resource "coder_app" "codex" {
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "5.4.1"
version = "5.4.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_ai_gateway = true
Expand All @@ -88,7 +88,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.1"
version = "5.4.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
openai_api_key = var.openai_api_key
Expand Down Expand Up @@ -116,7 +116,7 @@ module "codex" {
```

> [!NOTE]
> Servers configured through `mcp` or `mcp_config_remote_path` are appended to `~/.codex/config.toml`, so they apply to every Codex session in the workspace. Each remote URL should return a body in Codex's native TOML format, e.g.:
> Servers configured through `mcp` or `mcp_config_remote_path` are appended to `$CODEX_HOME/config.toml` (default: `~/.codex/config.toml`), so they apply to every Codex session in the workspace. Each remote URL should return a body in Codex's native TOML format, e.g.:
>
> ```toml
> [mcp_servers.my-tool]
Expand All @@ -134,7 +134,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.1"
version = "5.4.2"
agent_id = coder_agent.main.id
openai_api_key = var.openai_api_key
}
Expand All @@ -157,6 +157,8 @@ resource "coder_script" "post_codex" {

## Configuration

The module writes configuration to `$CODEX_HOME/config.toml` when `CODEX_HOME` is non-empty, and to `$HOME/.codex/config.toml` otherwise. Set `CODEX_HOME` in the environment used by both the install script and the Codex CLI.

When no custom `base_config_toml` is provided, the module uses a minimal default with `preferred_auth_method = "apikey"`. For advanced options, see [Codex config docs](https://developers.openai.com/codex/config-advanced).

When `openai_api_key` is set, the module authenticates with `codex login --with-api-key` over standard input. The key remains in the `OPENAI_API_KEY` workspace environment variable and is not rendered into the install script or written to `auth.json` by the module.
Expand Down
56 changes: 56 additions & 0 deletions registry/coder-labs/modules/codex/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
runTerraformApply,
runTerraformInit,
TerraformState,
writeFileContainer,
} from "~test";
import {
extractCoderEnvVars,
Expand Down Expand Up @@ -393,6 +394,61 @@ describe("codex", async () => {
expect(resp).not.toContain("model_reasoning_effort");
});

test("CODEX_HOME uses a custom directory containing spaces", async () => {
const codexHome = "/home/coder/state/codex home";
const baseConfig = 'model_reasoning_effort = "medium"';
const { id, scripts } = await setup({
moduleVariables: {
base_config_toml: baseConfig,
},
});
const defaultConfig = "/home/coder/.codex/config.toml";
const original = 'model_reasoning_effort = "high"\n';
expect(
(await execContainer(id, ["mkdir", "-p", path.dirname(defaultConfig)]))
.exitCode,
).toBe(0);
await writeFileContainer(id, defaultConfig, original);

await runScripts(id, scripts, { CODEX_HOME: codexHome });
const config = await readFileContainer(id, `${codexHome}/config.toml`);
expect(config).toContain(baseConfig);
expect(await readFileContainer(id, defaultConfig)).toBe(original);
});

test.each([
["unset", undefined],
["empty", ""],
])(
"CODEX_HOME %s falls back to HOME/.codex with spaces",
async (_scenario, codexHome) => {
const userHome = "/home/coder/home with spaces";
const { id, scripts } = await setup();
await runScripts(id, scripts);

// Run the materialized install script directly to isolate config paths
// from coder-utils' shell wrapper and explicitly unset CODEX_HOME.
const result = await execContainer(id, [
"env",
"-u",
"CODEX_HOME",
`HOME=${userHome}`,
...(codexHome === undefined ? [] : [`CODEX_HOME=${codexHome}`]),
"/home/coder/.coder-modules/coder-labs/codex/scripts/install.sh",
]);
if (result.exitCode !== 0) {
console.log(result.stdout);
console.log(result.stderr);
}
expect(result.exitCode).toBe(0);
const config = await readFileContainer(
id,
`${userHome}/.codex/config.toml`,
);
expect(config).toContain('preferred_auth_method = "apikey"');
},
);

test("pre-post-install-scripts", async () => {
const { id, scripts } = await setup({
moduleVariables: {
Expand Down
2 changes: 1 addition & 1 deletion registry/coder-labs/modules/codex/scripts/install.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ EOF
}

function populate_config_toml() {
local config_path="$HOME/.codex/config.toml"
local config_path="$${CODEX_HOME:-$HOME/.codex}/config.toml"
mkdir -p "$(dirname "$${config_path}")"

local MANAGED_START="# >>> coder-managed: codex module >>>"
Expand Down