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 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>OpenAI icon</title><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
\ 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..3d9b0f42
--- /dev/null
+++ b/registry/coder-labs/modules/codex/README.md
@@ -0,0 +1,102 @@
+---
+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
+  agentapi_version = "v0.3.3"
+  folder           = "/home/coder/project"
+}
+```
+
+## Prerequisites
+
+- You must add the [Coder Login](https://registry.coder.com/modules/coder-login/coder) 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"
+}
+```
+
+> **Security Notice**: This module uses the `--dangerously-bypass-approvals-and-sandbox` flag when running Codex CLI. This flag
+> bypasses standard permission checks and allows Codex CLI broader access to your system than normally permitted. While
+> this enables more functionality, it also means Codex CLI can potentially execute commands with the same privileges as
+> the user running it. 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`
+- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
+- **Environment**: Sets `OPENAI_API_KEY` and `CODEX_MODEL` for the CLI (if variables provided)
+
+## 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..a290325f
--- /dev/null
+++ b/registry/coder-labs/modules/codex/main.test.ts
@@ -0,0 +1,283 @@
+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<void>)[] = [];
+const registerCleanup = (cleanup: () => Promise<void>) => {
+  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<string, string>;
+  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(
+      `Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: ${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..5dc0e64c
--- /dev/null
+++ b/registry/coder-labs/modules/codex/main.tf
@@ -0,0 +1,176 @@
+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/code.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 "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.3.3"
+}
+
+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.0"
+
+  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)}' \
+    /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..bf0458ee
--- /dev/null
+++ b/registry/coder-labs/modules/codex/scripts/install.sh
@@ -0,0 +1,155 @@
+#!/bin/bash
+
+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_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 "
+${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
\ No newline at end of file
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..6c36ecea
--- /dev/null
+++ b/registry/coder-labs/modules/codex/scripts/start.sh
@@ -0,0 +1,76 @@
+#!/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=("--skip-git-repo-check")
+
+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="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
+    CODEX_ARGS+=("--dangerously-bypass-approvals-and-sandbox" "$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 63 --term-height 930 -- codex "${CODEX_ARGS[@]}"
\ No newline at end of file
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..989abd1e
--- /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
\ No newline at end of file
