Last week, I was staring down a tedious refactoring task: migrating a legacy Express.js API from CommonJS require statements to ES Modules across 47 files. I estimated it would take me a full day of mind-numbing find-and-replace work. Instead of grinding through it manually, I decided it was finally time to give OpenAI's Codex a real test drive.
I'd been hearing about Codex as an autonomous coding agent—something that doesn't just suggest code snippets, but actually writes, runs, and iterates on code in a sandboxed environment. After spending a few days using it to chew through that refactoring and several other tasks, I've developed a solid workflow. Here's a practical guide to getting started, including the bumps I hit along the way.
Choosing Your Interface
The first thing you need to know is that Codex isn't just one thing—there are four ways to use it:
- The desktop app (great if you hate the terminal)
- The VS Code extension (my daily driver for integrated work)
- The CLI (perfect for quick, focused tasks)
- Codex Cloud (runs entirely on OpenAI's infrastructure)
I started with the VS Code extension because I spend 90% of my day in Code anyway, but I quickly found myself gravitating toward the CLI for batch operations. There's something satisfying about typing a command and watching Codex spin up a sandbox, write the code, run the tests, and hand you back the result.
Installation and Setup
Let's walk through the CLI setup, since it's the most universally applicable.
First, install it via npm:
npm install -g @openai/codex
Next, you need to set your OpenAI API key. If you've used the OpenAI API before, you probably already have one, but make sure it has access to the models Codex needs (o3-mini or o4-mini, depending on your tier).
export OPENAI_API_KEY="sk-your-key-here"
I made the mistake of using an old API key that didn't have access to the newer reasoning models. Codex just threw a vague authentication error without explaining why. After ten minutes of confusion, I generated a fresh key in the OpenAI dashboard and everything worked. Learn from my frustration: use a current key with the right model access.
The Interview: Choosing Your Model and Reasoning Level
Here's something that caught me off guard: when you first start prompting Codex, it doesn't just blindly execute. It interviews you.
When you kick off a task, Codex asks you to choose both your model and its reasoning level. This is a critical decision that I initially glossed over.
- Model: You'll typically choose between o3-mini and o4-mini. The o4-mini model is faster and cheaper, while o3-mini has deeper reasoning capabilities for complex tasks.
- Reasoning level: This controls how much "thinking" Codex does before acting. Low reasoning is fast but shallow; high reasoning takes longer but handles complex logic better.
For my CommonJS to ESM migration, I went with o3-mini at high reasoning. For simpler tasks like "add JSDoc comments to these utility functions," o4-mini at low reasoning was plenty.
My First Real Task: The Migration
Let me show you exactly how I used Codex for that refactoring task.
I navigated to my project directory and ran:
codex "Migrate all .js files in the src/ directory from CommonJS require/module.exports to ES Module import/export syntax. Update package.json to include 'type': 'module'. Make sure all file imports include .js extensions in the import paths."
What happened next was impressive. Codex:
- Read through the project structure
- Identified all 47 files that needed changes
- Created a plan of action
- Executed the changes in its sandbox
- Ran my existing test suite to verify nothing broke
- Presented me with a diff of all changes
The whole process took about four minutes. My manual estimate was eight hours.
But here's where it got interesting: the tests failed. Codex had missed that a few files used dynamic require() calls with template literals, which can't be simply converted to static import statements. Instead of giving up, Codex read the test output, identified the specific failures, and refactored those dynamic imports into import() expressions with proper error handling.
This iterative loop—write, test, read errors, fix—is what makes Codex feel fundamentally different from a simple code generator. It actually responds to reality.
A Simpler Example: Bug Fixing
The next day, I had a bug where a date formatting function was returning "undefined" for certain timezone offsets. I fed it to Codex:
codex "The function formatDateUTC in src/utils/date.js returns 'undefined' when the timezone offset is negative. Fix the bug and add a test case that covers negative offsets."
Codex found the issue in under a minute: the function was using Math.sign() to determine offset direction but hadn't handled the case where Math.sign() returns -1. It fixed the logic and added a proper test case. Clean, fast, and correct.
Practical Tips After a Week of Use
After using Codex for several days on different projects, here are the patterns that consistently got me better results:
Be specific about scope. "Refactor the authentication module" is too vague. "Extract the JWT validation logic from server.js into a separate middleware file at src/middleware/auth.js" gives Codex a clear target.
Mention your testing setup. Codex runs tests to verify its work, but it needs to know how. Tell it: "Run tests with npm test" or "Use the Vitest framework."
Provide context about conventions. If your project uses specific naming conventions, directory structures, or patterns, say so. I wasted a prompt once because Codex created files following standard conventions that conflicted with my team's unusual folder structure.
Start with high reasoning for architecture, low for mechanics. If you're asking Codex to design a new feature, crank up the reasoning. If you're asking it to add missing semicolons, keep it low and fast.
Review the diffs before accepting. Codex is good, but it's not infallible. I always review changes before merging them into my codebase. It once introduced a subtle race condition in an async function that looked correct at first glance but would have caused intermittent failures under load.
Honest Limitations
Codex isn't magic, and there are real constraints to be aware of:
- It works best on self-contained tasks. If a change requires understanding deep, implicit business logic that isn't documented in the code, Codex will struggle. It reads your code, not your mind.
- Large codebases can be slow. When I pointed Codex at a monorepo with hundreds of files, it took significantly longer to orient itself. Breaking tasks into smaller, scoped prompts worked much better.
- The sandbox has limits. Codex runs code in an isolated environment. If your tests depend on external services, databases, or specific environment variables, you'll need to mock those or provide setup instructions.
- It's not a replacement for understanding your own code. I've seen developers accept Codex output without reading it, which is a recipe for technical debt. Use it as a force multiplier, not a replacement for code review.
Final Thoughts
That CommonJS to ESM migration? I reviewed Codex's output in about 30 minutes, found two minor issues (the dynamic import error handling was slightly different than I would have written, and one file had an unnecessary import), and shipped it the same afternoon. What would have been a miserable full-day task became a couple of hours including review.
Codex has earned a permanent spot in my workflow, particularly for tedious, well-defined tasks that don't require deep domain knowledge but do require careful, consistent execution across many files. For architectural decisions and complex business logic, I still do the thinking myself—but I let Codex handle the heavy lifting of turning those decisions into code.