Skip to content

feat: codex qol updates #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0c5d450
refactor: streamline Codex configuration variables and update install…
DevelopmentCats Aug 20, 2025
0fdb5aa
test: update codex configuration tests with new base config and addit…
DevelopmentCats Aug 20, 2025
223e21e
docs: update README for Codex module with updated usage examples and …
DevelopmentCats Aug 20, 2025
451f488
chore: update Codex module version to 1.0.2 in README
DevelopmentCats Aug 20, 2025
730c023
refactor: update defaults to fit tasks behaviour, and make mcp env a …
DevelopmentCats Aug 20, 2025
44d7dd7
feat: add writable_roots to minimal default configuration
DevelopmentCats Aug 20, 2025
c0ad3e1
feat: update writable_roots in minimal config and manage AGENTS.md fo…
DevelopmentCats Aug 20, 2025
50e5fdb
feat: update AGENTS.md path to ~/.codex directory and enhance README …
DevelopmentCats Aug 20, 2025
700789e
chore: polish and cleanup
DevelopmentCats Aug 20, 2025
212a62d
feat: update README to include sandbox_mode for containerized workspaces
DevelopmentCats Aug 20, 2025
f01ea69
chore: final cleanup, and updates in tests
DevelopmentCats Aug 21, 2025
5924646
chore: remove unecessary variables in codex module examples since the…
DevelopmentCats Aug 21, 2025
d567e20
chore: remove issue link from terminal height adjustment comment in s…
DevelopmentCats Aug 21, 2025
706b398
chore: remove writable_roots from sandbox_workspace_write configurati…
DevelopmentCats Aug 21, 2025
49ef776
Update README.md
DevelopmentCats Aug 21, 2025
b7db5fb
Update README.md
DevelopmentCats Aug 21, 2025
b4f453a
Update README.md
DevelopmentCats Aug 21, 2025
a630350
Update README.md
DevelopmentCats Aug 21, 2025
a271751
chore: change sec notice to gfm alert
DevelopmentCats Aug 21, 2025
04e760e
chore: update version to 2.0.0 for major version bump
DevelopmentCats Aug 21, 2025
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
113 changes: 52 additions & 61 deletions registry/coder-labs/modules/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.1"
version = "2.0.0"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
folder = "/home/coder/project"
Expand All @@ -25,29 +25,24 @@ module "codex" {
- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template
- OpenAI API key for Codex access

## Usage Example
## Examples

- Simple usage Example:
### Run standalone

```tf
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.1"
agent_id = coder_agent.example.id
openai_api_key = "..."
codex_model = "o4-mini"
install_codex = true
codex_version = "latest"
folder = "/home/coder/project"
codex_system_prompt = "You are a helpful coding assistant. Start every response with `Codex says:`"
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.0.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
folder = "/home/coder/project"
}
```

- Example usage with Tasks:
### Tasks integration

```tf
# This
data "coder_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
Expand All @@ -64,79 +59,75 @@ module "coder-login" {
}

module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
folder = "/home/coder/project"
approval_policy = "never" # Full auto mode
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.0.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
folder = "/home/coder/project"

# Custom configuration for full auto mode
base_config_toml = <<-EOT
approval_policy = "never"
preferred_auth_method = "apikey"
EOT
}
```

> [!WARNING]
> **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.
> 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.

## How it Works

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

## Sandbox Configuration

The module automatically configures Codex with a secure sandbox that allows AI tasks to work effectively:

- **Sandbox Mode**: `workspace-write` - Allows Codex to read/write files in the specified `folder`
- **Approval Policy**: `on-request` - Codex asks for permission before performing potentially risky operations
- **Network Access**: Enabled within the workspace for package installation and API calls

### Customizing Sandbox Behavior
## Configuration

You can customize the sandbox behavior using dedicated variables:
### Default Configuration

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

For most use cases, use the dedicated sandbox variables:
```toml
sandbox_mode = "workspace-write"
approval_policy = "never"
preferred_auth_method = "apikey"

```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
# ... other variables ...

# Containerized environments (fixes Landlock errors)
sandbox_mode = "danger-full-access"

# Or for read-only mode
# sandbox_mode = "read-only"

# Or for full auto mode
# approval_policy = "never"

# Or disable network access
# network_access = false
}
[sandbox_workspace_write]
network_access = true
```

#### **Using extra_codex_settings_toml (Advanced)**
### Custom Configuration

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

```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.0.0"
# ... other variables ...

extra_codex_settings_toml = <<-EOT
# Any custom Codex configuration
model = "gpt-4"
disable_response_storage = true
# Override default configuration
base_config_toml = <<-EOT
sandbox_mode = "danger-full-access"
approval_policy = "never"
preferred_auth_method = "apikey"
EOT

# Add extra MCP servers
additional_mcp_servers = <<-EOT
[mcp_servers.GitHub]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
type = "stdio"
EOT
}
```

> [!NOTE]
> 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.
> 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).

## Troubleshooting

Expand All @@ -150,6 +141,6 @@ module "codex" {

## References

- [OpenAI API Documentation](https://platform.openai.com/docs)
- [Codex CLI Documentation](https://github.com/openai/codex)
- [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
141 changes: 114 additions & 27 deletions registry/coder-labs/modules/codex/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,25 @@ describe("codex", async () => {
await expectAgentAPIStarted(id);
});

test("codex-config-toml", async () => {
const settings = dedent`
[mcp_servers.CustomMCP]
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
args = ["exp", "mcp", "server", "app-status-slug=codex"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
enabled = true
type = "stdio"
test("base-config-toml", async () => {
const baseConfig = dedent`
sandbox_mode = "danger-full-access"
approval_policy = "never"
preferred_auth_method = "apikey"

[custom_section]
new_feature = true
`.trim();
const { id } = await setup({
moduleVariables: {
extra_codex_settings_toml: settings,
base_config_toml: baseConfig,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]");
expect(resp).toContain("sandbox_mode = \"danger-full-access\"");
expect(resp).toContain("preferred_auth_method = \"apikey\"");
expect(resp).toContain("[custom_section]");
expect(resp).toContain("[mcp_servers.Coder]");
});

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

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

test("additional-extensions", async () => {
test("additional-mcp-servers", async () => {
const additional = dedent`
[mcp_servers.CustomMCP]
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
args = ["exp", "mcp", "server", "app-status-slug=codex"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
enabled = true
[mcp_servers.GitHub]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
type = "stdio"
description = "GitHub integration"

[mcp_servers.FileSystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
type = "stdio"
description = "File system access"
`.trim();
const { id } = await setup({
moduleVariables: {
additional_mcp_servers: additional,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.GitHub]");
expect(resp).toContain("[mcp_servers.FileSystem]");
expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("GitHub integration");
});

test("full-custom-config", async () => {
const baseConfig = dedent`
sandbox_mode = "read-only"
approval_policy = "untrusted"
preferred_auth_method = "chatgpt"
custom_setting = "test-value"

[advanced_settings]
timeout = 30000
debug = true
logging_level = "verbose"
`.trim();

const additionalMCP = dedent`
[mcp_servers.CustomTool]
command = "/usr/local/bin/custom-tool"
args = ["--serve", "--port", "8080"]
type = "stdio"
description = "Custom development tool"

[mcp_servers.DatabaseMCP]
command = "python"
args = ["-m", "database_mcp_server"]
type = "stdio"
description = "Database query interface"
`.trim();

const { id } = await setup({
moduleVariables: {
additional_extensions: additional,
base_config_toml: baseConfig,
additional_mcp_servers: additionalMCP,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]");

// Check base config
expect(resp).toContain("sandbox_mode = \"read-only\"");
expect(resp).toContain("preferred_auth_method = \"chatgpt\"");
expect(resp).toContain("custom_setting = \"test-value\"");
expect(resp).toContain("[advanced_settings]");
expect(resp).toContain("logging_level = \"verbose\"");

// Check MCP servers
expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("[mcp_servers.CustomTool]");
expect(resp).toContain("[mcp_servers.DatabaseMCP]");
expect(resp).toContain("Custom development tool");
expect(resp).toContain("Database query interface");
});

test("minimal-default-config", async () => {
const { id } = await setup({
moduleVariables: {
// No base_config_toml or additional_mcp_servers - should use defaults
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");

// Check default base config
expect(resp).toContain("sandbox_mode = \"workspace-write\"");
expect(resp).toContain("approval_policy = \"never\"");
expect(resp).toContain("[sandbox_workspace_write]");
expect(resp).toContain("network_access = true");

// Check only Coder MCP server is present
expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("Report ALL tasks and statuses");

// Ensure no additional MCP servers
const mcpServerCount = (resp.match(/\[mcp_servers\./g) || []).length;
expect(mcpServerCount).toBe(1);
});

test("codex-system-prompt", async () => {
Expand All @@ -210,7 +292,7 @@ describe("codex", async () => {
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
expect(resp).toContain(prompt);
});

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

const { id } = await setup({
Expand All @@ -233,7 +316,7 @@ describe("codex", async () => {
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
expect(resp).toContain(prompt_1);
expect(resp).toContain(prompt_2);

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

test("start-without-prompt", async () => {
const { id } = await setup();
const { id } = await setup({
moduleVariables: {
codex_system_prompt: "", // Explicitly disable system prompt
},
});
await execModuleScript(id);
const prompt = await execContainer(id, [
"ls",
"-l",
"/home/coder/AGENTS.md",
"/home/coder/.codex/AGENTS.md",
]);
expect(prompt.exitCode).not.toBe(0);
expect(prompt.stderr).toContain("No such file or directory");
Expand Down
Loading