Skip to content

Commit d6ae51f

Browse files
feat: codex qol updates (#348)
## Description - Removed variables for hardcoded configuration options, and replaced with variables for base config, and additional mcp servers. - Set module defaults so that this will run with minimal module configuration for tasks, while allowing further configuration if needed by the user for codex through the base configuration. - Updated tests for expected responses and new configuration options. - Move all codex related files outside of project folder. <!-- Briefly describe what this PR does and why --> ## Type of Change - [ ] New module - [ ] Bug fix - [X] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder-labs/modules/codex` **New version:** `v1.1.0` **Breaking change:** [X] Yes [ ] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> --------- Co-authored-by: Atif Ali <[email protected]>
1 parent eca289b commit d6ae51f

File tree

5 files changed

+251
-215
lines changed

5 files changed

+251
-215
lines changed

registry/coder-labs/modules/codex/README.md

Lines changed: 52 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
1313
```tf
1414
module "codex" {
1515
source = "registry.coder.com/coder-labs/codex/coder"
16-
version = "1.0.1"
16+
version = "2.0.0"
1717
agent_id = coder_agent.example.id
1818
openai_api_key = var.openai_api_key
1919
folder = "/home/coder/project"
@@ -25,29 +25,24 @@ module "codex" {
2525
- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template
2626
- OpenAI API key for Codex access
2727

28-
## Usage Example
28+
## Examples
2929

30-
- Simple usage Example:
30+
### Run standalone
3131

3232
```tf
3333
module "codex" {
34-
count = data.coder_workspace.me.start_count
35-
source = "registry.coder.com/coder-labs/codex/coder"
36-
version = "1.0.1"
37-
agent_id = coder_agent.example.id
38-
openai_api_key = "..."
39-
codex_model = "o4-mini"
40-
install_codex = true
41-
codex_version = "latest"
42-
folder = "/home/coder/project"
43-
codex_system_prompt = "You are a helpful coding assistant. Start every response with `Codex says:`"
34+
count = data.coder_workspace.me.start_count
35+
source = "registry.coder.com/coder-labs/codex/coder"
36+
version = "2.0.0"
37+
agent_id = coder_agent.example.id
38+
openai_api_key = "..."
39+
folder = "/home/coder/project"
4440
}
4541
```
4642

47-
- Example usage with Tasks:
43+
### Tasks integration
4844

4945
```tf
50-
# This
5146
data "coder_parameter" "ai_prompt" {
5247
type = "string"
5348
name = "AI Prompt"
@@ -64,79 +59,75 @@ module "coder-login" {
6459
}
6560
6661
module "codex" {
67-
source = "registry.coder.com/coder-labs/codex/coder"
68-
agent_id = coder_agent.example.id
69-
openai_api_key = "..."
70-
ai_prompt = data.coder_parameter.ai_prompt.value
71-
folder = "/home/coder/project"
72-
approval_policy = "never" # Full auto mode
62+
source = "registry.coder.com/coder-labs/codex/coder"
63+
version = "2.0.0"
64+
agent_id = coder_agent.example.id
65+
openai_api_key = "..."
66+
ai_prompt = data.coder_parameter.ai_prompt.value
67+
folder = "/home/coder/project"
68+
69+
# Custom configuration for full auto mode
70+
base_config_toml = <<-EOT
71+
approval_policy = "never"
72+
preferred_auth_method = "apikey"
73+
EOT
7374
}
7475
```
7576

7677
> [!WARNING]
77-
> **Security Notice**: This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified folder. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module in trusted environments and be aware of the security implications.
78+
> This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified folder. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module _only_ in trusted environments and be aware of the security implications.
7879
7980
## How it Works
8081

8182
- **Install**: The module installs Codex CLI and sets up the environment
82-
- **System Prompt**: If `codex_system_prompt` and `folder` are set, creates the directory (if needed) and writes the prompt to `AGENTS.md`
83+
- **System Prompt**: If `codex_system_prompt` is set, writes the prompt to `AGENTS.md` in the `~/.codex/` directory
8384
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
8485
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided)
8586

86-
## Sandbox Configuration
87-
88-
The module automatically configures Codex with a secure sandbox that allows AI tasks to work effectively:
89-
90-
- **Sandbox Mode**: `workspace-write` - Allows Codex to read/write files in the specified `folder`
91-
- **Approval Policy**: `on-request` - Codex asks for permission before performing potentially risky operations
92-
- **Network Access**: Enabled within the workspace for package installation and API calls
93-
94-
### Customizing Sandbox Behavior
87+
## Configuration
9588

96-
You can customize the sandbox behavior using dedicated variables:
89+
### Default Configuration
9790

98-
#### **Using Dedicated Variables (Recommended)**
91+
When no custom `base_config_toml` is provided, the module uses these secure defaults:
9992

100-
For most use cases, use the dedicated sandbox variables:
93+
```toml
94+
sandbox_mode = "workspace-write"
95+
approval_policy = "never"
96+
preferred_auth_method = "apikey"
10197

102-
```tf
103-
module "codex" {
104-
source = "registry.coder.com/coder-labs/codex/coder"
105-
# ... other variables ...
106-
107-
# Containerized environments (fixes Landlock errors)
108-
sandbox_mode = "danger-full-access"
109-
110-
# Or for read-only mode
111-
# sandbox_mode = "read-only"
112-
113-
# Or for full auto mode
114-
# approval_policy = "never"
115-
116-
# Or disable network access
117-
# network_access = false
118-
}
98+
[sandbox_workspace_write]
99+
network_access = true
119100
```
120101

121-
#### **Using extra_codex_settings_toml (Advanced)**
102+
### Custom Configuration
122103

123-
For advanced configuration or when you need to override multiple settings:
104+
For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_servers`:
124105

125106
```tf
126107
module "codex" {
127-
source = "registry.coder.com/coder-labs/codex/coder"
108+
source = "registry.coder.com/coder-labs/codex/coder"
109+
version = "2.0.0"
128110
# ... other variables ...
129111
130-
extra_codex_settings_toml = <<-EOT
131-
# Any custom Codex configuration
132-
model = "gpt-4"
133-
disable_response_storage = true
112+
# Override default configuration
113+
base_config_toml = <<-EOT
114+
sandbox_mode = "danger-full-access"
115+
approval_policy = "never"
116+
preferred_auth_method = "apikey"
117+
EOT
118+
119+
# Add extra MCP servers
120+
additional_mcp_servers = <<-EOT
121+
[mcp_servers.GitHub]
122+
command = "npx"
123+
args = ["-y", "@modelcontextprotocol/server-github"]
124+
type = "stdio"
134125
EOT
135126
}
136127
```
137128

138129
> [!NOTE]
139-
> The dedicated variables (`sandbox_mode`, `approval_policy`, `network_access`) are the recommended way to configure sandbox behavior. Use `extra_codex_settings_toml` only for advanced configuration that isn't covered by the dedicated variables.
130+
> If no custom configuration is provided, the module uses secure defaults. The Coder MCP server is always included automatically. For containerized workspaces (Docker/Kubernetes), you may need `sandbox_mode = "danger-full-access"` to avoid permission issues. For advanced options, see [Codex config docs](https://github.com/openai/codex/blob/main/codex-rs/config.md).
140131
141132
## Troubleshooting
142133

@@ -150,6 +141,6 @@ module "codex" {
150141
151142
## References
152143

153-
- [OpenAI API Documentation](https://platform.openai.com/docs)
144+
- [Codex CLI Documentation](https://github.com/openai/codex)
154145
- [AgentAPI Documentation](https://github.com/coder/agentapi)
155146
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)

registry/coder-labs/modules/codex/main.test.ts

Lines changed: 114 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,25 @@ describe("codex", async () => {
108108
await expectAgentAPIStarted(id);
109109
});
110110

111-
test("codex-config-toml", async () => {
112-
const settings = dedent`
113-
[mcp_servers.CustomMCP]
114-
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
115-
args = ["exp", "mcp", "server", "app-status-slug=codex"]
116-
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
117-
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
118-
enabled = true
119-
type = "stdio"
111+
test("base-config-toml", async () => {
112+
const baseConfig = dedent`
113+
sandbox_mode = "danger-full-access"
114+
approval_policy = "never"
115+
preferred_auth_method = "apikey"
116+
117+
[custom_section]
118+
new_feature = true
120119
`.trim();
121120
const { id } = await setup({
122121
moduleVariables: {
123-
extra_codex_settings_toml: settings,
122+
base_config_toml: baseConfig,
124123
},
125124
});
126125
await execModuleScript(id);
127126
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
128-
expect(resp).toContain("[mcp_servers.CustomMCP]");
127+
expect(resp).toContain("sandbox_mode = \"danger-full-access\"");
128+
expect(resp).toContain("preferred_auth_method = \"apikey\"");
129+
expect(resp).toContain("[custom_section]");
129130
expect(resp).toContain("[mcp_servers.Coder]");
130131
});
131132

@@ -142,7 +143,7 @@ describe("codex", async () => {
142143
id,
143144
"/home/coder/.codex-module/agentapi-start.log",
144145
);
145-
expect(resp).toContain("openai_api_key provided !");
146+
expect(resp).toContain("OpenAI API Key: Provided");
146147
});
147148

148149
test("pre-post-install-scripts", async () => {
@@ -181,25 +182,106 @@ describe("codex", async () => {
181182
expect(resp).toContain(folder);
182183
});
183184

184-
test("additional-extensions", async () => {
185+
test("additional-mcp-servers", async () => {
185186
const additional = dedent`
186-
[mcp_servers.CustomMCP]
187-
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
188-
args = ["exp", "mcp", "server", "app-status-slug=codex"]
189-
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
190-
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
191-
enabled = true
187+
[mcp_servers.GitHub]
188+
command = "npx"
189+
args = ["-y", "@modelcontextprotocol/server-github"]
190+
type = "stdio"
191+
description = "GitHub integration"
192+
193+
[mcp_servers.FileSystem]
194+
command = "npx"
195+
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
196+
type = "stdio"
197+
description = "File system access"
198+
`.trim();
199+
const { id } = await setup({
200+
moduleVariables: {
201+
additional_mcp_servers: additional,
202+
},
203+
});
204+
await execModuleScript(id);
205+
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
206+
expect(resp).toContain("[mcp_servers.GitHub]");
207+
expect(resp).toContain("[mcp_servers.FileSystem]");
208+
expect(resp).toContain("[mcp_servers.Coder]");
209+
expect(resp).toContain("GitHub integration");
210+
});
211+
212+
test("full-custom-config", async () => {
213+
const baseConfig = dedent`
214+
sandbox_mode = "read-only"
215+
approval_policy = "untrusted"
216+
preferred_auth_method = "chatgpt"
217+
custom_setting = "test-value"
218+
219+
[advanced_settings]
220+
timeout = 30000
221+
debug = true
222+
logging_level = "verbose"
223+
`.trim();
224+
225+
const additionalMCP = dedent`
226+
[mcp_servers.CustomTool]
227+
command = "/usr/local/bin/custom-tool"
228+
args = ["--serve", "--port", "8080"]
229+
type = "stdio"
230+
description = "Custom development tool"
231+
232+
[mcp_servers.DatabaseMCP]
233+
command = "python"
234+
args = ["-m", "database_mcp_server"]
192235
type = "stdio"
236+
description = "Database query interface"
193237
`.trim();
238+
194239
const { id } = await setup({
195240
moduleVariables: {
196-
additional_extensions: additional,
241+
base_config_toml: baseConfig,
242+
additional_mcp_servers: additionalMCP,
197243
},
198244
});
199245
await execModuleScript(id);
200246
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
201-
expect(resp).toContain("[mcp_servers.CustomMCP]");
247+
248+
// Check base config
249+
expect(resp).toContain("sandbox_mode = \"read-only\"");
250+
expect(resp).toContain("preferred_auth_method = \"chatgpt\"");
251+
expect(resp).toContain("custom_setting = \"test-value\"");
252+
expect(resp).toContain("[advanced_settings]");
253+
expect(resp).toContain("logging_level = \"verbose\"");
254+
255+
// Check MCP servers
202256
expect(resp).toContain("[mcp_servers.Coder]");
257+
expect(resp).toContain("[mcp_servers.CustomTool]");
258+
expect(resp).toContain("[mcp_servers.DatabaseMCP]");
259+
expect(resp).toContain("Custom development tool");
260+
expect(resp).toContain("Database query interface");
261+
});
262+
263+
test("minimal-default-config", async () => {
264+
const { id } = await setup({
265+
moduleVariables: {
266+
// No base_config_toml or additional_mcp_servers - should use defaults
267+
},
268+
});
269+
await execModuleScript(id);
270+
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
271+
272+
// Check default base config
273+
expect(resp).toContain("sandbox_mode = \"workspace-write\"");
274+
expect(resp).toContain("approval_policy = \"never\"");
275+
expect(resp).toContain("[sandbox_workspace_write]");
276+
expect(resp).toContain("network_access = true");
277+
278+
// Check only Coder MCP server is present
279+
expect(resp).toContain("[mcp_servers.Coder]");
280+
expect(resp).toContain("Report ALL tasks and statuses");
281+
282+
// Ensure no additional MCP servers
283+
const mcpServerCount = (resp.match(/\[mcp_servers\./g) || []).length;
284+
expect(mcpServerCount).toBe(1);
203285
});
204286

205287
test("codex-system-prompt", async () => {
@@ -210,7 +292,7 @@ describe("codex", async () => {
210292
},
211293
});
212294
await execModuleScript(id);
213-
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
295+
const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
214296
expect(resp).toContain(prompt);
215297
});
216298

@@ -223,7 +305,8 @@ describe("codex", async () => {
223305
`.trim();
224306
const pre_install_script = dedent`
225307
#!/bin/bash
226-
echo -e "${prompt_3}" >> /home/coder/AGENTS.md
308+
mkdir -p /home/coder/.codex
309+
echo -e "${prompt_3}" >> /home/coder/.codex/AGENTS.md
227310
`.trim();
228311

229312
const { id } = await setup({
@@ -233,7 +316,7 @@ describe("codex", async () => {
233316
},
234317
});
235318
await execModuleScript(id);
236-
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
319+
const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
237320
expect(resp).toContain(prompt_1);
238321
expect(resp).toContain(prompt_2);
239322

@@ -245,7 +328,7 @@ describe("codex", async () => {
245328
},
246329
});
247330
await execModuleScript(id_2);
248-
const resp_2 = await readFileContainer(id_2, "/home/coder/AGENTS.md");
331+
const resp_2 = await readFileContainer(id_2, "/home/coder/.codex/AGENTS.md");
249332
expect(resp_2).toContain(prompt_1);
250333
const count = (resp_2.match(new RegExp(prompt_1, "g")) || []).length;
251334
expect(count).toBe(1);
@@ -268,12 +351,16 @@ describe("codex", async () => {
268351
});
269352

270353
test("start-without-prompt", async () => {
271-
const { id } = await setup();
354+
const { id } = await setup({
355+
moduleVariables: {
356+
codex_system_prompt: "", // Explicitly disable system prompt
357+
},
358+
});
272359
await execModuleScript(id);
273360
const prompt = await execContainer(id, [
274361
"ls",
275362
"-l",
276-
"/home/coder/AGENTS.md",
363+
"/home/coder/.codex/AGENTS.md",
277364
]);
278365
expect(prompt.exitCode).not.toBe(0);
279366
expect(prompt.stderr).toContain("No such file or directory");

0 commit comments

Comments
 (0)