diff --git a/.icons/openai.svg b/.icons/openai.svg
new file mode 100644
index 00000000..3b4eff96
--- /dev/null
+++ b/.icons/openai.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md
new file mode 100644
index 00000000..b3f0a65d
--- /dev/null
+++ b/registry/coder-labs/modules/codex/README.md
@@ -0,0 +1,155 @@
+---
+display_name: Codex CLI
+icon: ../../../../.icons/openai.svg
+description: Run Codex CLI in your workspace with AgentAPI integration
+verified: true
+tags: [agent, codex, ai, openai, tasks]
+---
+
+# Codex CLI
+
+Run Codex CLI in your workspace to access OpenAI's models through the Codex interface, with custom pre/post install scripts. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for Coder Tasks compatibility.
+
+```tf
+module "codex" {
+ source = "registry.coder.com/coder-labs/codex/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.example.id
+ openai_api_key = var.openai_api_key
+ folder = "/home/coder/project"
+}
+```
+
+## Prerequisites
+
+- 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
+
+- Simple usage Example:
+
+```tf
+module "codex" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/coder-labs/codex/coder"
+ version = "1.0.0"
+ 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:`"
+}
+```
+
+- Example usage with Tasks:
+
+```tf
+# This
+data "coder_parameter" "ai_prompt" {
+ type = "string"
+ name = "AI Prompt"
+ default = ""
+ description = "Initial prompt for the Codex CLI"
+ mutable = true
+}
+
+module "coder-login" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/coder/coder-login/coder"
+ version = "1.0.31"
+ agent_id = coder_agent.example.id
+}
+
+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
+}
+```
+
+> [!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.
+
+## 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`
+- **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
+
+You can customize the sandbox behavior using dedicated variables:
+
+#### **Using Dedicated Variables (Recommended)**
+
+For most use cases, use the dedicated sandbox variables:
+
+```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
+}
+```
+
+#### **Using extra_codex_settings_toml (Advanced)**
+
+For advanced configuration or when you need to override multiple settings:
+
+```tf
+module "codex" {
+ source = "registry.coder.com/coder-labs/codex/coder"
+ # ... other variables ...
+
+ extra_codex_settings_toml = <<-EOT
+ # Any custom Codex configuration
+ model = "gpt-4"
+ disable_response_storage = true
+ 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.
+
+## Troubleshooting
+
+- Check installation and startup logs in `~/.codex-module/`
+- Ensure your OpenAI API key has access to the specified model
+
+> [!IMPORTANT]
+> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set, and **you create a `coder_parameter` named `"AI Prompt"` and pass its value to the codex module's `ai_prompt` variable**. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
+> The module automatically configures Codex with your API key and model preferences.
+> folder is a required variable for the module to function correctly.
+
+## References
+
+- [OpenAI API Documentation](https://platform.openai.com/docs)
+- [AgentAPI Documentation](https://github.com/coder/agentapi)
+- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts
new file mode 100644
index 00000000..ec2d9dbe
--- /dev/null
+++ b/registry/coder-labs/modules/codex/main.test.ts
@@ -0,0 +1,281 @@
+import {
+ test,
+ afterEach,
+ describe,
+ setDefaultTimeout,
+ beforeAll,
+ expect,
+} from "bun:test";
+import { execContainer, readFileContainer, runTerraformInit } from "~test";
+import {
+ loadTestFile,
+ writeExecutable,
+ setup as setupUtil,
+ execModuleScript,
+ expectAgentAPIStarted,
+} from "../../../coder/modules/agentapi/test-util";
+import dedent from "dedent";
+
+let cleanupFunctions: (() => Promise)[] = [];
+const registerCleanup = (cleanup: () => Promise) => {
+ cleanupFunctions.push(cleanup);
+};
+afterEach(async () => {
+ const cleanupFnsCopy = cleanupFunctions.slice().reverse();
+ cleanupFunctions = [];
+ for (const cleanup of cleanupFnsCopy) {
+ try {
+ await cleanup();
+ } catch (error) {
+ console.error("Error during cleanup:", error);
+ }
+ }
+});
+
+interface SetupProps {
+ skipAgentAPIMock?: boolean;
+ skipCodexMock?: boolean;
+ moduleVariables?: Record;
+ agentapiMockScript?: string;
+}
+
+const setup = async (props?: SetupProps): Promise<{ id: string }> => {
+ const projectDir = "/home/coder/project";
+ const { id } = await setupUtil({
+ moduleDir: import.meta.dir,
+ moduleVariables: {
+ install_codex: props?.skipCodexMock ? "true" : "false",
+ install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
+ codex_model: "gpt-4-turbo",
+ folder: "/home/coder",
+ ...props?.moduleVariables,
+ },
+ registerCleanup,
+ projectDir,
+ skipAgentAPIMock: props?.skipAgentAPIMock,
+ agentapiMockScript: props?.agentapiMockScript,
+ });
+ if (!props?.skipCodexMock) {
+ await writeExecutable({
+ containerId: id,
+ filePath: "/usr/bin/codex",
+ content: await loadTestFile(import.meta.dir, "codex-mock.sh"),
+ });
+ }
+ return { id };
+};
+
+setDefaultTimeout(60 * 1000);
+
+describe("codex", async () => {
+ beforeAll(async () => {
+ await runTerraformInit(import.meta.dir);
+ });
+
+ test("happy-path", async () => {
+ const { id } = await setup();
+ await execModuleScript(id);
+ await expectAgentAPIStarted(id);
+ });
+
+ test("install-codex-version", async () => {
+ const version_to_install = "0.10.0";
+ const { id } = await setup({
+ skipCodexMock: true,
+ moduleVariables: {
+ install_codex: "true",
+ codex_version: version_to_install,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await execContainer(id, [
+ "bash",
+ "-c",
+ `cat /home/coder/.codex-module/install.log`,
+ ]);
+ expect(resp.stdout).toContain(version_to_install);
+ });
+
+ test("check-latest-codex-version-works", async () => {
+ const { id } = await setup({
+ skipCodexMock: true,
+ skipAgentAPIMock: true,
+ moduleVariables: {
+ install_codex: "true",
+ },
+ });
+ await execModuleScript(id);
+ 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"
+ `.trim();
+ const { id } = await setup({
+ moduleVariables: {
+ extra_codex_settings_toml: settings,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
+ expect(resp).toContain("[mcp_servers.CustomMCP]");
+ expect(resp).toContain("[mcp_servers.Coder]");
+ });
+
+ test("codex-api-key", async () => {
+ const apiKey = "test-api-key-123";
+ const { id } = await setup({
+ moduleVariables: {
+ openai_api_key: apiKey,
+ },
+ });
+ await execModuleScript(id);
+
+ const resp = await readFileContainer(
+ id,
+ "/home/coder/.codex-module/agentapi-start.log",
+ );
+ expect(resp).toContain("openai_api_key provided !");
+ });
+
+ test("pre-post-install-scripts", async () => {
+ const { id } = await setup({
+ moduleVariables: {
+ pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
+ post_install_script: "#!/bin/bash\necho 'post-install-script'",
+ },
+ });
+ await execModuleScript(id);
+ const preInstallLog = await readFileContainer(
+ id,
+ "/home/coder/.codex-module/pre_install.log",
+ );
+ expect(preInstallLog).toContain("pre-install-script");
+ const postInstallLog = await readFileContainer(
+ id,
+ "/home/coder/.codex-module/post_install.log",
+ );
+ expect(postInstallLog).toContain("post-install-script");
+ });
+
+ test("folder-variable", async () => {
+ const folder = "/tmp/codex-test-folder";
+ const { id } = await setup({
+ skipCodexMock: false,
+ moduleVariables: {
+ folder,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await readFileContainer(
+ id,
+ "/home/coder/.codex-module/install.log",
+ );
+ expect(resp).toContain(folder);
+ });
+
+ test("additional-extensions", 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
+ type = "stdio"
+ `.trim();
+ const { id } = await setup({
+ moduleVariables: {
+ additional_extensions: additional,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
+ expect(resp).toContain("[mcp_servers.CustomMCP]");
+ expect(resp).toContain("[mcp_servers.Coder]");
+ });
+
+ test("codex-system-prompt", async () => {
+ const prompt = "This is a system prompt for Codex.";
+ const { id } = await setup({
+ moduleVariables: {
+ codex_system_prompt: prompt,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
+ expect(resp).toContain(prompt);
+ });
+
+ test("codex-system-prompt-skip-append-if-exists", async () => {
+ const prompt_1 = "This is a system prompt for Codex.";
+ const prompt_2 = "This is a system prompt for Goose.";
+ const prompt_3 = dedent`
+ This is a system prompt for Codex.
+ This is a system prompt for Gemini.
+ `.trim();
+ const pre_install_script = dedent`
+ #!/bin/bash
+ echo -e "${prompt_3}" >> /home/coder/AGENTS.md
+ `.trim();
+
+ const { id } = await setup({
+ moduleVariables: {
+ pre_install_script,
+ codex_system_prompt: prompt_2,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
+ expect(resp).toContain(prompt_1);
+ expect(resp).toContain(prompt_2);
+
+ // Re-run with a prompt that already exists, it should not append again
+ const { id: id_2 } = await setup({
+ moduleVariables: {
+ pre_install_script,
+ codex_system_prompt: prompt_1,
+ },
+ });
+ await execModuleScript(id_2);
+ const resp_2 = await readFileContainer(id_2, "/home/coder/AGENTS.md");
+ expect(resp_2).toContain(prompt_1);
+ const count = (resp_2.match(new RegExp(prompt_1, "g")) || []).length;
+ expect(count).toBe(1);
+ });
+
+ test("codex-ai-task-prompt", async () => {
+ const prompt = "This is a system prompt for Codex.";
+ const { id } = await setup({
+ moduleVariables: {
+ ai_prompt: prompt,
+ },
+ });
+ await execModuleScript(id);
+ const resp = await execContainer(id, [
+ "bash",
+ "-c",
+ `cat /home/coder/.codex-module/agentapi-start.log`,
+ ]);
+ expect(resp.stdout).toContain(prompt);
+ });
+
+ test("start-without-prompt", async () => {
+ const { id } = await setup();
+ await execModuleScript(id);
+ const prompt = await execContainer(id, [
+ "ls",
+ "-l",
+ "/home/coder/AGENTS.md",
+ ]);
+ expect(prompt.exitCode).not.toBe(0);
+ expect(prompt.stderr).toContain("No such file or directory");
+ });
+});
diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf
new file mode 100644
index 00000000..d17d4891
--- /dev/null
+++ b/registry/coder-labs/modules/codex/main.tf
@@ -0,0 +1,207 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 2.7"
+ }
+ }
+}
+
+variable "agent_id" {
+ type = string
+ description = "The ID of a Coder agent."
+}
+
+data "coder_workspace" "me" {}
+
+data "coder_workspace_owner" "me" {}
+
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
+ default = null
+}
+
+variable "group" {
+ type = string
+ description = "The name of a group that this app belongs to."
+ default = null
+}
+
+variable "icon" {
+ type = string
+ description = "The icon to use for the app."
+ default = "/icon/openai.svg"
+}
+
+variable "folder" {
+ type = string
+ description = "The folder to run Codex in."
+}
+
+variable "install_codex" {
+ type = bool
+ description = "Whether to install Codex."
+ default = true
+}
+
+variable "codex_version" {
+ type = string
+ description = "The version of Codex to install."
+ default = "" # empty string means the latest available version
+}
+
+variable "extra_codex_settings_toml" {
+ type = string
+ description = "Settings to append to ~/.codex/config.toml."
+ default = ""
+}
+
+variable "sandbox_mode" {
+ type = string
+ description = "The sandbox mode for Codex. Options: workspace-write, read-only, danger-full-access."
+ default = "workspace-write"
+ validation {
+ condition = contains(["workspace-write", "read-only", "danger-full-access"], var.sandbox_mode)
+ error_message = "sandbox_mode must be one of: workspace-write, read-only, danger-full-access."
+ }
+}
+
+variable "approval_policy" {
+ type = string
+ description = "The approval policy for Codex. Options: on-request, never, untrusted."
+ default = "on-request"
+ validation {
+ condition = contains(["on-request", "never", "untrusted"], var.approval_policy)
+ error_message = "approval_policy must be one of: on-request, never, untrusted."
+ }
+}
+
+variable "network_access" {
+ type = bool
+ description = "Whether to allow network access in workspace-write mode."
+ default = true
+}
+
+variable "openai_api_key" {
+ type = string
+ description = "Codex API Key"
+ default = ""
+}
+
+variable "install_agentapi" {
+ type = bool
+ description = "Whether to install AgentAPI."
+ default = true
+}
+
+variable "agentapi_version" {
+ type = string
+ description = "The version of AgentAPI to install."
+ default = "v0.5.0"
+}
+
+variable "codex_model" {
+ type = string
+ description = "The model for Codex to use (e.g., o4-mini)."
+ default = ""
+}
+
+variable "pre_install_script" {
+ type = string
+ description = "Custom script to run before installing Codex."
+ default = null
+}
+
+variable "post_install_script" {
+ type = string
+ description = "Custom script to run after installing Codex."
+ default = null
+}
+
+variable "ai_prompt" {
+ type = string
+ description = "Task prompt for the Codex CLI"
+ default = ""
+}
+
+variable "additional_extensions" {
+ type = string
+ description = "Additional extensions configuration in json format to append to the config."
+ default = ""
+}
+
+variable "codex_system_prompt" {
+ type = string
+ description = "System prompt for Codex. It will be added to AGENTS.md in the specified folder."
+ default = ""
+}
+
+
+
+resource "coder_env" "openai_api_key" {
+ agent_id = var.agent_id
+ name = "OPENAI_API_KEY"
+ value = var.openai_api_key
+}
+
+locals {
+ app_slug = "codex"
+ install_script = file("${path.module}/scripts/install.sh")
+ start_script = file("${path.module}/scripts/start.sh")
+ module_dir_name = ".codex-module"
+}
+
+module "agentapi" {
+ source = "registry.coder.com/coder/agentapi/coder"
+ version = "1.1.1"
+
+ agent_id = var.agent_id
+ web_app_slug = local.app_slug
+ web_app_order = var.order
+ web_app_group = var.group
+ web_app_icon = var.icon
+ web_app_display_name = "Codex"
+ cli_app_slug = "${local.app_slug}-cli"
+ cli_app_display_name = "Codex CLI"
+ module_dir_name = local.module_dir_name
+ install_agentapi = var.install_agentapi
+ agentapi_version = var.agentapi_version
+ pre_install_script = var.pre_install_script
+ post_install_script = var.post_install_script
+ start_script = <<-EOT
+ #!/bin/bash
+ set -o errexit
+ set -o pipefail
+
+ echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
+ chmod +x /tmp/start.sh
+ ARG_OPENAI_API_KEY='${var.openai_api_key}' \
+ ARG_CODEX_MODEL='${var.codex_model}' \
+ ARG_CODEX_START_DIRECTORY='${var.folder}' \
+ ARG_CODEX_TASK_PROMPT='${base64encode(var.ai_prompt)}' \
+ /tmp/start.sh
+ EOT
+
+ install_script = <<-EOT
+ #!/bin/bash
+ set -o errexit
+ set -o pipefail
+
+ echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
+ chmod +x /tmp/install.sh
+ ARG_INSTALL='${var.install_codex}' \
+ ARG_CODEX_VERSION='${var.codex_version}' \
+ ARG_EXTRA_CODEX_CONFIG='${base64encode(var.extra_codex_settings_toml)}' \
+ ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \
+ ARG_ADDITIONAL_EXTENSIONS='${base64encode(var.additional_extensions)}' \
+ ARG_CODEX_START_DIRECTORY='${var.folder}' \
+ ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
+ ARG_SANDBOX_MODE='${var.sandbox_mode}' \
+ ARG_APPROVAL_POLICY='${var.approval_policy}' \
+ ARG_NETWORK_ACCESS='${var.network_access}' \
+ /tmp/install.sh
+ EOT
+}
\ No newline at end of file
diff --git a/registry/coder-labs/modules/codex/scripts/install.sh b/registry/coder-labs/modules/codex/scripts/install.sh
new file mode 100644
index 00000000..d06dd4fd
--- /dev/null
+++ b/registry/coder-labs/modules/codex/scripts/install.sh
@@ -0,0 +1,171 @@
+#!/bin/bash
+source "$HOME"/.bashrc
+
+BOLD='\033[0;1m'
+
+# Function to check if a command exists
+command_exists() {
+ command -v "$1" > /dev/null 2>&1
+}
+set -o errexit
+set -o pipefail
+set -o nounset
+
+ARG_EXTRA_CODEX_CONFIG=$(echo -n "$ARG_EXTRA_CODEX_CONFIG" | base64 -d)
+ARG_ADDITIONAL_EXTENSIONS=$(echo -n "$ARG_ADDITIONAL_EXTENSIONS" | base64 -d)
+ARG_CODEX_INSTRUCTION_PROMPT=$(echo -n "$ARG_CODEX_INSTRUCTION_PROMPT" | base64 -d)
+
+echo "--------------------------------"
+printf "install: %s\n" "$ARG_INSTALL"
+printf "codex_version: %s\n" "$ARG_CODEX_VERSION"
+printf "codex_config: %s\n" "$ARG_EXTRA_CODEX_CONFIG"
+printf "app_slug: %s\n" "$ARG_CODER_MCP_APP_STATUS_SLUG"
+printf "additional_extensions: %s\n" "$ARG_ADDITIONAL_EXTENSIONS"
+printf "start_directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
+printf "instruction_prompt: %s\n" "$ARG_CODEX_INSTRUCTION_PROMPT"
+
+echo "--------------------------------"
+
+set +o nounset
+
+function install_node() {
+ # borrowed from claude-code module
+ if ! command_exists npm; then
+ printf "npm not found, checking for Node.js installation...\n"
+ if ! command_exists node; then
+ printf "Node.js not found, installing Node.js via NVM...\n"
+ export NVM_DIR="$HOME/.nvm"
+ if [ ! -d "$NVM_DIR" ]; then
+ mkdir -p "$NVM_DIR"
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ else
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ fi
+
+ nvm install --lts
+ nvm use --lts
+ nvm alias default node
+
+ printf "Node.js installed: %s\n" "$(node --version)"
+ printf "npm installed: %s\n" "$(npm --version)"
+ else
+ printf "Node.js is installed but npm is not available. Please install npm manually.\n"
+ exit 1
+ fi
+ fi
+}
+
+function install_codex() {
+ if [ "${ARG_INSTALL}" = "true" ]; then
+ # we need node to install and run codex-cli
+ install_node
+
+ # If nvm does not exist, we will create a global npm directory (this os to prevent the possibility of EACCESS issues on npm -g)
+ if ! command_exists nvm; then
+ printf "which node: %s\n" "$(which node)"
+ printf "which npm: %s\n" "$(which npm)"
+
+ # Create a directory for global packages
+ mkdir -p "$HOME"/.npm-global
+
+ # Configure npm to use it
+ npm config set prefix "$HOME/.npm-global"
+
+ # Add to PATH for current session
+ export PATH="$HOME/.npm-global/bin:$PATH"
+
+ # Add to shell profile for future sessions
+ if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" ~/.bashrc; then
+ echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> ~/.bashrc
+ fi
+ fi
+
+ printf "%s Installing Codex CLI\n" "${BOLD}"
+
+ if [ -n "$ARG_CODEX_VERSION" ]; then
+ npm install -g "@openai/codex@$ARG_CODEX_VERSION"
+ else
+ npm install -g "@openai/codex"
+ fi
+ printf "%s Successfully installed Codex CLI. Version: %s\n" "${BOLD}" "$(codex --version)"
+ fi
+}
+
+function populate_config_toml() {
+ CONFIG_PATH="$HOME/.codex/config.toml"
+ mkdir -p "$(dirname "$CONFIG_PATH")"
+ printf "Custom codex_config is provided !\n"
+ BASE_SANDBOX_CONFIG=$(
+ cat << EOF
+# Base sandbox configuration for Codex workspace access
+# This ensures Codex can read/write files in the specified folder for AI tasks
+sandbox_mode = "${ARG_SANDBOX_MODE}"
+approval_policy = "${ARG_APPROVAL_POLICY}"
+
+# Allow network access in workspace-write mode for package installation, API calls, etc.
+[sandbox_workspace_write]
+network_access = ${ARG_NETWORK_ACCESS}
+EOF
+ )
+
+ BASE_EXTENSIONS=$(
+ cat << EOF
+[mcp_servers.Coder]
+command = "coder"
+args = ["exp", "mcp", "server"]
+env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284", "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}" }
+description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
+type = "stdio"
+EOF
+ )
+
+ echo "
+${BASE_SANDBOX_CONFIG}
+
+${ARG_EXTRA_CODEX_CONFIG}
+
+${BASE_EXTENSIONS}
+
+${ARG_ADDITIONAL_EXTENSIONS}
+ " > "$HOME/.codex/config.toml"
+
+}
+
+function add_instruction_prompt_if_exists() {
+ if [ -n "${ARG_CODEX_INSTRUCTION_PROMPT:-}" ]; then
+ if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
+ printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ cd "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+ else
+ printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+ cd "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+ fi
+
+ # Check if AGENTS.md contains the instruction prompt already
+ if [ -f AGENTS.md ] && grep -Fxq "${ARG_CODEX_INSTRUCTION_PROMPT}" AGENTS.md; then
+ printf "AGENTS.md already contains the instruction prompt. Skipping append.\n"
+ else
+ printf "Appending instruction prompt to AGENTS.md\n"
+ echo -e "\n${ARG_CODEX_INSTRUCTION_PROMPT}" >> AGENTS.md
+ fi
+ else
+ printf "AGENTS.md is not set.\n"
+ fi
+}
+
+# Install Codex
+install_codex
+codex --version
+populate_config_toml
+add_instruction_prompt_if_exists
diff --git a/registry/coder-labs/modules/codex/scripts/start.sh b/registry/coder-labs/modules/codex/scripts/start.sh
new file mode 100644
index 00000000..ea3449c1
--- /dev/null
+++ b/registry/coder-labs/modules/codex/scripts/start.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+# Load shell environment
+source "$HOME"/.bashrc
+set -o errexit
+set -o pipefail
+command_exists() {
+ command -v "$1" > /dev/null 2>&1
+}
+
+if [ -f "$HOME/.nvm/nvm.sh" ]; then
+ source "$HOME"/.nvm/nvm.sh
+else
+ export PATH="$HOME/.npm-global/bin:$PATH"
+fi
+
+printf "Version: %s\n" "$(codex --version)"
+set -o nounset
+ARG_CODEX_TASK_PROMPT=$(echo -n "$ARG_CODEX_TASK_PROMPT" | base64 -d)
+
+echo "--------------------------------"
+printf "openai_api_key: %s\n" "$ARG_OPENAI_API_KEY"
+printf "codex_model: %s\n" "$ARG_CODEX_MODEL"
+printf "start_directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
+printf "task_prompt: %s\n" "$ARG_CODEX_TASK_PROMPT"
+echo "--------------------------------"
+set +o nounset
+CODEX_ARGS=()
+
+if command_exists codex; then
+ printf "Codex is installed\n"
+else
+ printf "Error: Codex is not installed. Please enable install_codex or install it manually\n"
+ exit 1
+fi
+
+if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
+ printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ cd "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+else
+ printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+ cd "${ARG_CODEX_START_DIRECTORY}" || {
+ printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
+ exit 1
+ }
+fi
+
+if [ -n "$ARG_CODEX_MODEL" ]; then
+ CODEX_ARGS+=("--model" "$ARG_CODEX_MODEL")
+fi
+
+
+
+if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
+ printf "Running the task prompt %s\n" "$ARG_CODEX_TASK_PROMPT"
+ PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
+ CODEX_ARGS+=("$PROMPT")
+else
+ printf "No task prompt given.\n"
+fi
+
+if [ -n "$ARG_OPENAI_API_KEY" ]; then
+ printf "openai_api_key provided !\n"
+else
+ printf "openai_api_key not provided\n"
+fi
+
+# use low width to fit in the tasks UI sidebar
+# we adjust the height to 930 due to a bug in codex, see: https://github.com/openai/codex/issues/1608
+printf "Starting codex with %s\n" "${CODEX_ARGS[@]}"
+agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}"
diff --git a/registry/coder-labs/modules/codex/testdata/codex-mock.sh b/registry/coder-labs/modules/codex/testdata/codex-mock.sh
new file mode 100644
index 00000000..8c1c7366
--- /dev/null
+++ b/registry/coder-labs/modules/codex/testdata/codex-mock.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+if [[ "$1" == "--version" ]]; then
+ echo "HELLO: $(bash -c env)"
+ echo "codex version v1.0.0"
+ exit 0
+fi
+
+set -e
+
+while true; do
+ echo "$(date) - codex-mock"
+ sleep 15
+done