NanoClaw started as an app effectively built by running Claude inside its own repo. Porting it to Codex meant rewriting not just the runtime, but the markdown instruction layer Claude had been using to build and operate the system.
Most "AI app ports" are not actually ports.
They are usually one of these:
- swap the SDK
- change the model name
- keep the old runtime assumptions
- call it done
This was not that.
On April 4, Anthropic began limiting Claude subscription usage for third-party harnesses, including tools in the OpenClaw/NanoClaw category, pushing that usage toward extra paid usage instead of normal subscription limits. That policy shift did not magically make NanoClaw unusable overnight, but it did make one thing obvious:
Building a serious agent system on top of a first-party subscription runtime is not a stable foundation.
NanoClaw started life as something much stranger and much more interesting: an agent harness that was not just running on Claude-native assumptions, but was also largely shaped by Claude while working inside the repo.
The original workflow was basically:
- point Claude Code at the repo
- give it a stack of markdown instructions
- run commands like
/setup - let it scaffold, extend, and evolve the app from inside its own preferred operating model
That means the old codebase was not merely "compatible with Claude."
It was, in a very real sense, built by running Claude inside a repo that was designed to teach Claude how to keep building it.
That distinction is the whole story.
Because once we decided to make NanoClaw truly Codex-native, the work was not "replace Anthropic with OpenAI."
The work was simple to describe and hard to do:
separate the app from the agent that originally co-authored its architecture.
The Weird Part: The Markdown Was Part of the Runtime
A lot of projects have docs.
NanoClaw had something closer to an instruction substrate.
The Claude-native version depended on a pile of markdown-driven behavior:
CLAUDE.md.claude/skills/...- setup flows like
/setup - branch/workflow docs
- installer docs
- capability docs
- hidden conventions for how the agent should extend the app
These files were not just "developer notes."
They were part of the product's behavioral layer.
They told the agent:
- how to initialize the app
- how to install integrations
- how to shape features
- how to think about memory
- how to extend channels
- how to apply skills
So when we started the port, one thing became obvious fast:
some of the .md files were effectively code.
Not executable code in the TypeScript sense, but runtime-significant architecture in the agent sense.
That meant the port had two layers:
- Port the actual Node.js/container runtime
- Audit and rewrite the instruction layer that Claude had been using to build and operate the app
If we didn't do both, we wouldn't have a Codex-native system.
We would just have Claude-shaped behavior hiding under a new logo.
What NanoClaw Actually Is
At its core, NanoClaw is a small Node.js orchestrator for running isolated assistants in containers.
The host process handles:
- channels
- routing
- SQLite state
- scheduling
- group registration
- IPC
- container lifecycle
The container handles:
- the actual agent runtime
- group-local context
- active skills
- delegated subagents
- tool access via MCP
That architecture survived the port.
What changed was the substrate it was built on.
What Was Claude-Specific
Before the port, the Claude-specific assumptions were everywhere:
- Claude SDK query loop
-
CLAUDE.mdinstruction contract -
.claude/state and session layout -
.claude/skills/install model - Claude slash-command workflows like
/setup - Claude-era delegation semantics
- remote control built around Claude tooling
And because Claude had been used to keep extending the app from inside the repo, those assumptions were reinforced both in code and in markdown.
So the first real task was not coding.
It was indexing and classifying the repo:
- which parts were host application behavior
- which parts were Claude-specific runtime behavior
- which parts were portable concepts wearing Claude-shaped names
- which markdown files were still product-significant
- which markdown files were now just historical scaffolding
That review step mattered more than any individual code patch.
Because if you misclassify those layers, you end up deleting real features or preserving the wrong abstractions.
The Port Was Not "Remove Claude, Add Codex"
A real port required four kinds of changes.
1. Replace the runtime contract
The old container runner was built around Claude-native execution.
The new one is built around Codex CLI running non-interactively in the container.
That meant:
- building prompt context from host state plus group instructions
- invoking Codex cleanly for fresh turns and resumed turns
- streaming results back through a stable host/container protocol
- preserving session continuity where possible
This sounds like a simple runner swap.
It wasn't.
Because CLI contracts differ in annoying, subtle ways.
We hit real bugs around things like:
- flags that work on
codexbut notcodex exec - flags accepted on fresh turns but rejected on
codex exec resume
Those are not theoretical portability problems. They are the kinds of issues that make a working-looking system fail in production after the host queue has already done its job.
2. Rewrite the instruction layer
This was the part that most clearly separated a fake port from a real one.
The Claude-native repo had accumulated behavior through markdown:
- skills
- setup flow
- integration guidance
- memory conventions
- extension/install patterns
We had to go through those files and make hard calls:
- convert
- prune
- delete
- replace
That meant moving from Claude-specific conventions to Codex-native ones:
-
CLAUDE.mdbecameAGENTS.md - repo/group skills became
SKILL.md-based - old
.claude/skills/assumptions were removed - the setup story was rewritten around the current runtime rather than old slash-command flows
This was not glamorous work, but it was absolutely core to the port.
If the old markdown continues to teach the wrong mental model to the next agent working in the repo, the architecture drifts backwards immediately.
3. Keep the real product features
One of the easiest mistakes in a port is to treat every old feature as vendor-specific and quietly delete it.
That would have been wrong here.
Some things were truly Claude-specific and should die:
- Claude SDK integration
- Claude remote control
- old Claude-only skill marketplace conventions
But other things were just app features that happened to be implemented in a Claude-native system:
- Telegram
- web UI channel
- subagent delegation
- host skills
Those needed to be ported, not removed.
That was an important correction during this work.
The right question was not:
"Does this mention Claude?"
The right question was:
"Is this a real app capability, and does Codex have a legitimate equivalent or integration path?"
4. Make the host own the architecture
This is the core lesson of the whole effort.
The clean architecture is:
- NanoClaw owns orchestration
- NanoClaw owns state
- NanoClaw owns channels
- NanoClaw owns skills
- NanoClaw owns setup
- Codex is the agent backend
If the host owns those things, the backend is replaceable.
If the backend owns those things, the host is just branding.
The Bugs That Proved the Point
The best part of a port like this is that the bugs are revealing.
They tell you where your assumptions still belong to the old system.
Bug 1: The bot looked alive but was brain-dead
Telegram connected.
Messages were being stored.
But nothing was being processed.
Why?
Because WhatsApp was loading even when it was not configured, entering a reconnect loop, and blocking the async startup path before the message loop came online.
That produced a nasty half-working state:
- inbound messages reached the DB
- logs looked active
-
startMessageLoop()never really came alive -
"NanoClaw running"never appeared
The fix was to stop treating channel startup as monolithic:
- load channels conditionally from real config/auth state
- allow channel connect failure without killing startup
- skip missing channels cleanly instead of blocking the process
That sounds basic, but it was exactly the kind of bug you get when a system has grown around agent-era assumptions rather than explicit host orchestration.
Bug 2: The auth existed, but Codex still 401'd
This was my favorite bug in the whole port because it was pure systems work.
The host was logged into Codex.
The container was supposed to inherit auth.
But every in-container turn failed with:
401 Unauthorized: Missing bearer or basic authentication in header
The credentials were not absent.
They were misplaced.
The chain was:
- auth was copied into a nested
.codex/.codex/auth.json - that directory was mounted into the container
- Codex expected auth relative to
$HOME/.codex/auth.json - the file existed on disk, just not where the CLI actually looked
This is the kind of bug that disappears if you talk only in architecture diagrams.
It only becomes obvious when you trace:
- host path
- mounted path
- container
$HOME - actual CLI lookup convention
The fix:
- flatten the per-group Codex state layout
- mount it directly to
/home/node/.codex - migrate old nested state forward
After that, the 401 vanished and the live message path finally completed.
Bug 3: Resume was not the same as fresh execution
Another seam bug.
We got fresh turns working, then resume failed because codex exec resume did not accept the same options as the fresh path.
That is a perfect example of why "we already integrated the CLI" is not enough.
You have to test:
- fresh turn path
- resumed turn path
- session restoration
- output handling after resume
Those are separate runtime contracts, even when they share most of the binary name.
What the System Looks Like Now
The current branch is not "Claude code with Codex paint."
It is a different runtime model.
Today it has:
- Codex-native container runner
-
AGENTS.mdfor project/group instructions -
SKILL.md-based host skills - per-group Codex state
- Telegram, WhatsApp, and Web UI channels
- Codex-backed delegation via MCP
- host-owned setup and orchestration
And critically, it is no longer relying on the repo being interpreted through Claude-native markdown conventions to keep itself evolving correctly.
That was the actual finish line.
Not "build passes."
Not "logs look nice."
But:
the app can now be operated, extended, and reasoned about as a Codex-native system.
The Real Lesson
The strongest takeaway from this work is that agent-built systems can end up with architecture embedded in places most teams don't normally treat as architecture.
In this case, that meant:
- markdown instruction files
- slash-command setup flows
- skill directories
- implied agent workflows
- hidden filesystem conventions
If you want to port a system like that, you have to audit all of it.
Not just the code.
Not just the API client.
All of it.
Because once an agent has spent enough time building inside a repo, the repo starts to encode assumptions about that agent's worldview.
That is fascinating when it works. It is dangerous when you need to switch runtimes.
If You're Thinking About Doing This Yourself
My advice is:
- inventory the instruction layer before you touch the runtime
- treat markdown conventions as potentially executable architecture
- separate host ownership from backend ownership early
- verify real CLI behavior instead of trusting docs from memory
- test startup, auth, fresh turns, resumed turns, and recovery independently
- do not call it a port if you quietly deleted capabilities that had real equivalents
The hard part is not replacing the model.
The hard part is identifying all the places where the old model had already shaped the app.
And once you do that, the job becomes much clearer:
you are not porting an SDK. You are reclaiming the architecture.
Top comments (0)