A local HTTP bridge that lets tools such as n8n submit tasks to the Codex CLI. It maintains a Codex app-server session, maps trusted project aliases to allowlisted directories, and forwards command or file-change approval requests to n8n.
index.js ESM entry point and public exports
src/config.js Environment and project-map configuration
src/logger.js JSON execution log writer
src/server.js Process startup and graceful shutdown
src/codex/app-server.js Codex JSON-RPC session and approval lifecycle
src/codex/decisions.js Approval decision validation
src/http/create-app.js Express routes and response formatting
test/index.test.js HTTP and service-boundary tests
The HTTP layer depends on a codexServer interface rather than creating child processes inside route handlers. This keeps route tests isolated from the installed Codex CLI and centralizes persistent JSON-RPC state in one service.
- A client sends a project alias and task to
POST /codex. - The server resolves the alias from the local
projectsallowlist. - A persistent Codex app-server starts a thread and turn in that directory with workspace-write sandboxing.
- Approval requests are sent to the configured n8n webhook and remain pending until
/approvals/:approvalIdreceives a decision. - When the turn completes, the server returns the output, diff, commands, approval timing, and execution timing as JSON.
On Windows, the runner starts codex.cmd app-server; on macOS, Linux, and Docker it starts codex app-server directly.
- Node.js 24 or newer (modern ECMAScript 2026 baseline)
- npm
- Codex CLI installed and available as
codexonPATH - Codex CLI authenticated and ready to run
Verify the prerequisites:
node --version
npm --version
codex --versionClone the repository and install its dependencies:
git clone https://github.com/anggadb/codex-cli-runner.git
cd codex-cli-runner
npm installSet PROJECTS_JSON in .env so every public alias points to an absolute directory that Codex is allowed to modify. For example, on Windows:
PROJECTS_JSON={"my-website":"C:\\path\\to\\my-website","my-api":"C:\\path\\to\\my-api"}The directory must already exist. The runner returns Project directory not found when an alias points to a missing path.
On macOS or Linux, use absolute POSIX paths instead:
PROJECTS_JSON={"my-website":"/path/to/my-website","my-api":"/path/to/my-api"}Start the server:
npm startnpm start loads .env when the file exists. Variables already present in the process environment take precedence. The API listens on http://127.0.0.1:3001 by default, which keeps it accessible only from the local machine.
Copy the example environment file and edit its host paths:
Copy-Item .env.example .envPROJECT_PATHis the host project directory exposed to Codex.CODEX_HOME_PATHis the host Codex configuration directory containing your existing authentication.PROJECTS_JSONmaps API aliases to paths inside the container. Its paths must match the volume targets incompose.yaml.PORTcontrols the host port; Compose publishes it on127.0.0.1only.
Build and start the service:
docker compose up --build -d
docker compose logs -f codex-runnerConfirm that Codex is installed inside the container:
docker compose exec codex-runner codex --versionStop the service with docker compose down. To allow more projects, add a volume for each host directory and add its container path to PROJECTS_JSON.
Every completed Codex turn writes a separate JSON file to logs/. Each entry contains its timestamp, identifiers, project alias, submitted task, status, timing, output, diff, commands, approval metrics, and any error. The task is stored in the log only and is not added to the HTTP response.
The logs/ directory is ignored by Git. Docker Compose mounts the same host directory at /app/logs, so container logs persist locally without being included in the image or repository.
Request body:
{
"project": "my-website",
"task": "Add a print-friendly stylesheet and verify the existing build"
}Example request with curl:
curl.exe -X POST http://127.0.0.1:3001/codex `
-H "Content-Type: application/json" `
-d '{"project":"my-website","task":"Add a print-friendly stylesheet"}'Example successful response:
{
"success": true,
"project": "my-website",
"taskId": "...",
"threadId": "...",
"turnId": "...",
"status": "completed",
"durationMs": 1250,
"output": "...Codex output...",
"diff": "",
"commands": [],
"error": ""
}Validation failures return HTTP 400:
{
"error": "Project not allowed"
}{
"error": "Task is required"
}Codex execution failures are represented by success: false with details in error.
Use an HTTP Request node with:
- Method:
POST - URL:
http://127.0.0.1:3001/codex - Body Content Type:
JSON - Body:
{
"project": "my-website",
"task": "={{ $json.task }}"
}If n8n runs in Docker, 127.0.0.1 refers to the n8n container rather than the host. The server binding and n8n URL will need to be adjusted for that network topology, with authentication added before exposing the service beyond localhost.
- Keep the server bound to localhost unless you add authentication, authorization, rate limiting, and transport security.
- Only add trusted directories to the
projectsallowlist. - Treat anyone who can call this endpoint as able to instruct Codex to modify allowlisted workspaces.
workspace-writelimits Codex through its sandbox, but tasks can still make substantial changes inside the selected workspace.- Output is buffered in memory and the HTTP request remains open until Codex exits. This implementation is best suited to short, local automation jobs.
- Only one synchronous request/response workflow is provided; there is no queue or job status endpoint.
- There is no request-size policy beyond Express defaults, cancellation endpoint, or output-size limit.
- Child-process startup errors are not currently returned through a dedicated handler.