<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: johnohhh1</title>
    <description>The latest articles on Forem by johnohhh1 (@johnohhh1).</description>
    <link>https://forem.com/johnohhh1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1381778%2F432c1fbe-76f1-4329-8c49-03d09c69e314.png</url>
      <title>Forem: johnohhh1</title>
      <link>https://forem.com/johnohhh1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnohhh1"/>
    <language>en</language>
    <item>
      <title>Anthropic Limited Claude Subscription Usage for Third-Party Harnesses. So We Ported NanoClaw to Codex</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Thu, 09 Apr 2026 19:01:29 +0000</pubDate>
      <link>https://forem.com/johnohhh1/anthropic-limited-claude-subscription-usage-for-third-party-harnesses-so-we-ported-nanoclaw-to-47ic</link>
      <guid>https://forem.com/johnohhh1/anthropic-limited-claude-subscription-usage-for-third-party-harnesses-so-we-ported-nanoclaw-to-47ic</guid>
      <description>&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most "AI app ports" are not actually ports.&lt;/p&gt;

&lt;p&gt;They are usually one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;swap the SDK&lt;/li&gt;
&lt;li&gt;change the model name&lt;/li&gt;
&lt;li&gt;keep the old runtime assumptions&lt;/li&gt;
&lt;li&gt;call it done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not that.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;Building a serious agent system on top of a first-party subscription runtime is not a stable foundation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;NanoClaw&lt;/code&gt; started life as something much stranger and much more interesting: an agent harness that was not just &lt;strong&gt;running on Claude-native assumptions&lt;/strong&gt;, but was also largely &lt;strong&gt;shaped by Claude while working inside the repo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The original workflow was basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;point Claude Code at the repo&lt;/li&gt;
&lt;li&gt;give it a stack of markdown instructions&lt;/li&gt;
&lt;li&gt;run commands like &lt;code&gt;/setup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;let it scaffold, extend, and evolve the app from inside its own preferred operating model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the old codebase was not merely "compatible with Claude."&lt;/p&gt;

&lt;p&gt;It was, in a very real sense, &lt;strong&gt;built by running Claude inside a repo that was designed to teach Claude how to keep building it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction is the whole story.&lt;/p&gt;

&lt;p&gt;Because once we decided to make NanoClaw truly Codex-native, the work was not "replace Anthropic with OpenAI."&lt;/p&gt;

&lt;p&gt;The work was simple to describe and hard to do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;separate the app from the agent that originally co-authored its architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weird Part: The Markdown Was Part of the Runtime
&lt;/h2&gt;

&lt;p&gt;A lot of projects have docs.&lt;/p&gt;

&lt;p&gt;NanoClaw had something closer to an instruction substrate.&lt;/p&gt;

&lt;p&gt;The Claude-native version depended on a pile of markdown-driven behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.claude/skills/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;setup flows like &lt;code&gt;/setup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;branch/workflow docs&lt;/li&gt;
&lt;li&gt;installer docs&lt;/li&gt;
&lt;li&gt;capability docs&lt;/li&gt;
&lt;li&gt;hidden conventions for how the agent should extend the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files were not just "developer notes."&lt;/p&gt;

&lt;p&gt;They were part of the product's behavioral layer.&lt;/p&gt;

&lt;p&gt;They told the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to initialize the app&lt;/li&gt;
&lt;li&gt;how to install integrations&lt;/li&gt;
&lt;li&gt;how to shape features&lt;/li&gt;
&lt;li&gt;how to think about memory&lt;/li&gt;
&lt;li&gt;how to extend channels&lt;/li&gt;
&lt;li&gt;how to apply skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when we started the port, one thing became obvious fast:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;some of the &lt;code&gt;.md&lt;/code&gt; files were effectively code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not executable code in the TypeScript sense, but runtime-significant architecture in the agent sense.&lt;/p&gt;

&lt;p&gt;That meant the port had two layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Port the actual Node.js/container runtime&lt;/li&gt;
&lt;li&gt;Audit and rewrite the instruction layer that Claude had been using to build and operate the app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If we didn't do both, we wouldn't have a Codex-native system.&lt;/p&gt;

&lt;p&gt;We would just have Claude-shaped behavior hiding under a new logo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NanoClaw Actually Is
&lt;/h2&gt;

&lt;p&gt;At its core, NanoClaw is a small Node.js orchestrator for running isolated assistants in containers.&lt;/p&gt;

&lt;p&gt;The host process handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;channels&lt;/li&gt;
&lt;li&gt;routing&lt;/li&gt;
&lt;li&gt;SQLite state&lt;/li&gt;
&lt;li&gt;scheduling&lt;/li&gt;
&lt;li&gt;group registration&lt;/li&gt;
&lt;li&gt;IPC&lt;/li&gt;
&lt;li&gt;container lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The container handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the actual agent runtime&lt;/li&gt;
&lt;li&gt;group-local context&lt;/li&gt;
&lt;li&gt;active skills&lt;/li&gt;
&lt;li&gt;delegated subagents&lt;/li&gt;
&lt;li&gt;tool access via MCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That architecture survived the port.&lt;/p&gt;

&lt;p&gt;What changed was the substrate it was built on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Claude-Specific
&lt;/h2&gt;

&lt;p&gt;Before the port, the Claude-specific assumptions were everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude SDK query loop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; instruction contract&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.claude/&lt;/code&gt; state and session layout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.claude/skills/&lt;/code&gt; install model&lt;/li&gt;
&lt;li&gt;Claude slash-command workflows like &lt;code&gt;/setup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Claude-era delegation semantics&lt;/li&gt;
&lt;li&gt;remote control built around Claude tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So the first real task was not coding.&lt;/p&gt;

&lt;p&gt;It was indexing and classifying the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which parts were host application behavior&lt;/li&gt;
&lt;li&gt;which parts were Claude-specific runtime behavior&lt;/li&gt;
&lt;li&gt;which parts were portable concepts wearing Claude-shaped names&lt;/li&gt;
&lt;li&gt;which markdown files were still product-significant&lt;/li&gt;
&lt;li&gt;which markdown files were now just historical scaffolding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That review step mattered more than any individual code patch.&lt;/p&gt;

&lt;p&gt;Because if you misclassify those layers, you end up deleting real features or preserving the wrong abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Port Was Not "Remove Claude, Add Codex"
&lt;/h2&gt;

&lt;p&gt;A real port required four kinds of changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Replace the runtime contract
&lt;/h3&gt;

&lt;p&gt;The old container runner was built around Claude-native execution.&lt;/p&gt;

&lt;p&gt;The new one is built around Codex CLI running non-interactively in the container.&lt;/p&gt;

&lt;p&gt;That meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building prompt context from host state plus group instructions&lt;/li&gt;
&lt;li&gt;invoking Codex cleanly for fresh turns and resumed turns&lt;/li&gt;
&lt;li&gt;streaming results back through a stable host/container protocol&lt;/li&gt;
&lt;li&gt;preserving session continuity where possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds like a simple runner swap.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;Because CLI contracts differ in annoying, subtle ways.&lt;/p&gt;

&lt;p&gt;We hit real bugs around things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flags that work on &lt;code&gt;codex&lt;/code&gt; but not &lt;code&gt;codex exec&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;flags accepted on fresh turns but rejected on &lt;code&gt;codex exec resume&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rewrite the instruction layer
&lt;/h3&gt;

&lt;p&gt;This was the part that most clearly separated a fake port from a real one.&lt;/p&gt;

&lt;p&gt;The Claude-native repo had accumulated behavior through markdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;setup flow&lt;/li&gt;
&lt;li&gt;integration guidance&lt;/li&gt;
&lt;li&gt;memory conventions&lt;/li&gt;
&lt;li&gt;extension/install patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We had to go through those files and make hard calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;convert&lt;/li&gt;
&lt;li&gt;prune&lt;/li&gt;
&lt;li&gt;delete&lt;/li&gt;
&lt;li&gt;replace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That meant moving from Claude-specific conventions to Codex-native ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; became &lt;code&gt;AGENTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;repo/group skills became &lt;code&gt;SKILL.md&lt;/code&gt;-based&lt;/li&gt;
&lt;li&gt;old &lt;code&gt;.claude/skills/&lt;/code&gt; assumptions were removed&lt;/li&gt;
&lt;li&gt;the setup story was rewritten around the current runtime rather than old slash-command flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not glamorous work, but it was absolutely core to the port.&lt;/p&gt;

&lt;p&gt;If the old markdown continues to teach the wrong mental model to the next agent working in the repo, the architecture drifts backwards immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep the real product features
&lt;/h3&gt;

&lt;p&gt;One of the easiest mistakes in a port is to treat every old feature as vendor-specific and quietly delete it.&lt;/p&gt;

&lt;p&gt;That would have been wrong here.&lt;/p&gt;

&lt;p&gt;Some things were truly Claude-specific and should die:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude SDK integration&lt;/li&gt;
&lt;li&gt;Claude remote control&lt;/li&gt;
&lt;li&gt;old Claude-only skill marketplace conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But other things were just app features that happened to be implemented in a Claude-native system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Telegram&lt;/li&gt;
&lt;li&gt;WhatsApp&lt;/li&gt;
&lt;li&gt;web UI channel&lt;/li&gt;
&lt;li&gt;subagent delegation&lt;/li&gt;
&lt;li&gt;host skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those needed to be ported, not removed.&lt;/p&gt;

&lt;p&gt;That was an important correction during this work.&lt;/p&gt;

&lt;p&gt;The right question was not:&lt;/p&gt;

&lt;p&gt;"Does this mention Claude?"&lt;/p&gt;

&lt;p&gt;The right question was:&lt;/p&gt;

&lt;p&gt;"Is this a real app capability, and does Codex have a legitimate equivalent or integration path?"&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Make the host own the architecture
&lt;/h3&gt;

&lt;p&gt;This is the core lesson of the whole effort.&lt;/p&gt;

&lt;p&gt;The clean architecture is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NanoClaw owns orchestration&lt;/li&gt;
&lt;li&gt;NanoClaw owns state&lt;/li&gt;
&lt;li&gt;NanoClaw owns channels&lt;/li&gt;
&lt;li&gt;NanoClaw owns skills&lt;/li&gt;
&lt;li&gt;NanoClaw owns setup&lt;/li&gt;
&lt;li&gt;Codex is the agent backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the host owns those things, the backend is replaceable.&lt;/p&gt;

&lt;p&gt;If the backend owns those things, the host is just branding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bugs That Proved the Point
&lt;/h2&gt;

&lt;p&gt;The best part of a port like this is that the bugs are revealing.&lt;/p&gt;

&lt;p&gt;They tell you where your assumptions still belong to the old system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 1: The bot looked alive but was brain-dead
&lt;/h3&gt;

&lt;p&gt;Telegram connected.&lt;br&gt;
Messages were being stored.&lt;br&gt;
But nothing was being processed.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That produced a nasty half-working state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inbound messages reached the DB&lt;/li&gt;
&lt;li&gt;logs looked active&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startMessageLoop()&lt;/code&gt; never really came alive&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"NanoClaw running"&lt;/code&gt; never appeared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix was to stop treating channel startup as monolithic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;load channels conditionally from real config/auth state&lt;/li&gt;
&lt;li&gt;allow channel connect failure without killing startup&lt;/li&gt;
&lt;li&gt;skip missing channels cleanly instead of blocking the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 2: The auth existed, but Codex still 401'd
&lt;/h3&gt;

&lt;p&gt;This was my favorite bug in the whole port because it was pure systems work.&lt;/p&gt;

&lt;p&gt;The host was logged into Codex.&lt;br&gt;
The container was supposed to inherit auth.&lt;br&gt;
But every in-container turn failed with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;401 Unauthorized: Missing bearer or basic authentication in header&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The credentials were not absent.&lt;br&gt;
They were misplaced.&lt;/p&gt;

&lt;p&gt;The chain was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth was copied into a nested &lt;code&gt;.codex/.codex/auth.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;that directory was mounted into the container&lt;/li&gt;
&lt;li&gt;Codex expected auth relative to &lt;code&gt;$HOME/.codex/auth.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the file existed on disk, just not where the CLI actually looked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of bug that disappears if you talk only in architecture diagrams.&lt;/p&gt;

&lt;p&gt;It only becomes obvious when you trace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;host path&lt;/li&gt;
&lt;li&gt;mounted path&lt;/li&gt;
&lt;li&gt;container &lt;code&gt;$HOME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;actual CLI lookup convention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flatten the per-group Codex state layout&lt;/li&gt;
&lt;li&gt;mount it directly to &lt;code&gt;/home/node/.codex&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;migrate old nested state forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, the 401 vanished and the live message path finally completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug 3: Resume was not the same as fresh execution
&lt;/h3&gt;

&lt;p&gt;Another seam bug.&lt;/p&gt;

&lt;p&gt;We got fresh turns working, then resume failed because &lt;code&gt;codex exec resume&lt;/code&gt; did not accept the same options as the fresh path.&lt;/p&gt;

&lt;p&gt;That is a perfect example of why "we already integrated the CLI" is not enough.&lt;/p&gt;

&lt;p&gt;You have to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fresh turn path&lt;/li&gt;
&lt;li&gt;resumed turn path&lt;/li&gt;
&lt;li&gt;session restoration&lt;/li&gt;
&lt;li&gt;output handling after resume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are separate runtime contracts, even when they share most of the binary name.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the System Looks Like Now
&lt;/h2&gt;

&lt;p&gt;The current branch is not "Claude code with Codex paint."&lt;/p&gt;

&lt;p&gt;It is a different runtime model.&lt;/p&gt;

&lt;p&gt;Today it has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Codex-native container runner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; for project/group instructions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SKILL.md&lt;/code&gt;-based host skills&lt;/li&gt;
&lt;li&gt;per-group Codex state&lt;/li&gt;
&lt;li&gt;Telegram, WhatsApp, and Web UI channels&lt;/li&gt;
&lt;li&gt;Codex-backed delegation via MCP&lt;/li&gt;
&lt;li&gt;host-owned setup and orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And critically, it is no longer relying on the repo being interpreted through Claude-native markdown conventions to keep itself evolving correctly.&lt;/p&gt;

&lt;p&gt;That was the actual finish line.&lt;/p&gt;

&lt;p&gt;Not "build passes."&lt;/p&gt;

&lt;p&gt;Not "logs look nice."&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the app can now be operated, extended, and reasoned about as a Codex-native system.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;In this case, that meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;markdown instruction files&lt;/li&gt;
&lt;li&gt;slash-command setup flows&lt;/li&gt;
&lt;li&gt;skill directories&lt;/li&gt;
&lt;li&gt;implied agent workflows&lt;/li&gt;
&lt;li&gt;hidden filesystem conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to port a system like that, you have to audit all of it.&lt;/p&gt;

&lt;p&gt;Not just the code.&lt;/p&gt;

&lt;p&gt;Not just the API client.&lt;/p&gt;

&lt;p&gt;All of it.&lt;/p&gt;

&lt;p&gt;Because once an agent has spent enough time building inside a repo, the repo starts to encode assumptions about &lt;em&gt;that agent's worldview&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is fascinating when it works. It is dangerous when you need to switch runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You're Thinking About Doing This Yourself
&lt;/h2&gt;

&lt;p&gt;My advice is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inventory the instruction layer before you touch the runtime&lt;/li&gt;
&lt;li&gt;treat markdown conventions as potentially executable architecture&lt;/li&gt;
&lt;li&gt;separate host ownership from backend ownership early&lt;/li&gt;
&lt;li&gt;verify real CLI behavior instead of trusting docs from memory&lt;/li&gt;
&lt;li&gt;test startup, auth, fresh turns, resumed turns, and recovery independently&lt;/li&gt;
&lt;li&gt;do not call it a port if you quietly deleted capabilities that had real equivalents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hard part is not replacing the model.&lt;/p&gt;

&lt;p&gt;The hard part is identifying all the places where the old model had already shaped the app.&lt;/p&gt;

&lt;p&gt;And once you do that, the job becomes much clearer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;you are not porting an SDK. You are reclaiming the architecture.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>nanoclaw</category>
      <category>codex</category>
      <category>port</category>
    </item>
    <item>
      <title>How I repackaged the official Windows Codex MSIX into a working Linux .deb</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:13:34 +0000</pubDate>
      <link>https://forem.com/johnohhh1/how-i-repackaged-the-official-windows-codex-msix-into-a-working-linux-deb-48ch</link>
      <guid>https://forem.com/johnohhh1/how-i-repackaged-the-official-windows-codex-msix-into-a-working-linux-deb-48ch</guid>
      <description>&lt;p&gt;I wanted a real Linux desktop app for Codex.&lt;/p&gt;

&lt;p&gt;At first, this looked like it should be straightforward: take the official Windows package, unpack it, swap in Linux Electron, rebuild whatever was platform-specific, and turn it into a &lt;code&gt;.deb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is not what happened.&lt;/p&gt;

&lt;p&gt;What actually worked was more pragmatic: preserve the official Codex payload, branding, and resources from the Windows MSIX, but replace the runtime shell with a small Linux Electron wrapper that opens Codex in its own desktop window.&lt;/p&gt;

&lt;p&gt;This post is the write-up I wish I had before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;The target was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install Codex on Ubuntu as a &lt;code&gt;.deb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;get a desktop launcher and icon&lt;/li&gt;
&lt;li&gt;make it feel like a native app&lt;/li&gt;
&lt;li&gt;avoid relying on a browser tab&lt;/li&gt;
&lt;li&gt;keep the build repeatable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had something similar working for ChatGPT, so I assumed Codex would follow the same path.&lt;/p&gt;

&lt;p&gt;That assumption was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I started with
&lt;/h2&gt;

&lt;p&gt;The source payload was the official Windows MSIX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI.Codex_26.313.5234.0_x64__2p2nqsd0c76g0.Msix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo I ended up with is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com:johnohhh1/codex-ubuntu.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main build script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./build-codex-native-deb.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The obvious approach
&lt;/h2&gt;

&lt;p&gt;The first plan was the normal "repackage the Electron app" playbook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract the MSIX.&lt;/li&gt;
&lt;li&gt;Grab the bundled &lt;code&gt;app.asar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Swap the Windows Electron runtime for Linux Electron.&lt;/li&gt;
&lt;li&gt;Rebuild or replace native modules.&lt;/li&gt;
&lt;li&gt;Repackage everything into a &lt;code&gt;.deb&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In theory, that should have produced a Linux &lt;code&gt;.deb&lt;/code&gt; built directly from the original app bundle.&lt;/p&gt;

&lt;p&gt;In practice, it failed in multiple different ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: the Windows bundle was not actually Linux-ready
&lt;/h2&gt;

&lt;p&gt;The Codex payload included resources and binaries that looked promising, but the packaged app still carried Windows assumptions.&lt;/p&gt;

&lt;p&gt;The biggest issue was native modules.&lt;/p&gt;

&lt;p&gt;At one point I confirmed the app was dying because a native dependency was still Windows-built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;invalid ELF header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the classic sign that Linux is trying to load a binary module compiled for the wrong platform.&lt;/p&gt;

&lt;p&gt;So the next move was to rebuild native modules for Linux and wire them back into the extracted app.&lt;/p&gt;

&lt;p&gt;That still did not get me to a usable app window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: getting the app to "launch" was not the same as getting it to work
&lt;/h2&gt;

&lt;p&gt;I got as far as installing the package cleanly and starting Electron on Linux.&lt;/p&gt;

&lt;p&gt;That was progress, but not enough.&lt;/p&gt;

&lt;p&gt;The app process would launch and then exit without showing a window, or fail during startup after getting past the first set of issues.&lt;/p&gt;

&lt;p&gt;This is one of the most annoying parts of Electron packaging work: "the binary started" sounds good, but it does not mean the app is actually viable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: Electron GPU startup was fatal on Linux
&lt;/h2&gt;

&lt;p&gt;Once I simplified the runtime enough to observe the real failure clearly, the next blocker showed up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FATAL: GPU process isn't usable. Goodbye.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changed the direction of the fix immediately.&lt;/p&gt;

&lt;p&gt;Instead of trying to preserve every part of the original desktop behavior, I needed a Linux-friendly launcher path that was stable in the actual Ubuntu session I was using.&lt;/p&gt;

&lt;p&gt;The fix was to disable GPU at both levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in the Electron app itself with command-line switches&lt;/li&gt;
&lt;li&gt;in the launcher wrapper used by the packaged &lt;code&gt;.deb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final launcher executes Electron like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exec&lt;/span&gt; /opt/codex-desktop-native/electron/Codex &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-sandbox&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-setuid-sandbox&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-gpu&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-gpu-compositing&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was not elegant, but it was effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot: stop trying to preserve the original runtime shell
&lt;/h2&gt;

&lt;p&gt;This was the real turning point.&lt;/p&gt;

&lt;p&gt;Instead of continuing to patch the original Windows desktop runtime deeper and deeper, I switched to a simpler architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use Linux Electron as the runtime&lt;/li&gt;
&lt;li&gt;keep the official Codex assets from the MSIX&lt;/li&gt;
&lt;li&gt;build a tiny Linux wrapper app&lt;/li&gt;
&lt;li&gt;load &lt;code&gt;https://chatgpt.com/codex&lt;/code&gt; inside a dedicated desktop window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approach still let me build from the official Windows MSIX, but it stopped me from getting trapped in the least portable parts of the original runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the final wrapper does
&lt;/h2&gt;

&lt;p&gt;The generated wrapper app is very small.&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates a &lt;code&gt;BrowserWindow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;hides the menu bar&lt;/li&gt;
&lt;li&gt;uses a Codex title and icon&lt;/li&gt;
&lt;li&gt;opens external popups in the default browser&lt;/li&gt;
&lt;li&gt;allows normal &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; navigation&lt;/li&gt;
&lt;li&gt;forces the window to show even if page paint is slow&lt;/li&gt;
&lt;li&gt;points at &lt;code&gt;https://chatgpt.com/codex&lt;/code&gt; by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrapper also supports overriding the URL with an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CODEX_WEB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://chatgpt.com/codex codex-desktop-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is useful if the endpoint ever changes or if you want a staging/test flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging details that mattered
&lt;/h2&gt;

&lt;p&gt;There were a few packaging details that turned out to be more important than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The app had to present itself as Codex
&lt;/h3&gt;

&lt;p&gt;I renamed the Linux Electron binary from &lt;code&gt;electron&lt;/code&gt; to &lt;code&gt;Codex&lt;/code&gt; so the app identity looked correct on Linux.&lt;/p&gt;

&lt;p&gt;That helped with launcher behavior and window class handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Desktop entry details mattered
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.desktop&lt;/code&gt; file sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Name=Codex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Exec=codex-desktop-native&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Icon=codex-desktop-native&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StartupWMClass=Codex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-GNOME-WMClass=Codex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that, you can end up with weird launcher grouping or desktop integration issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Old install leftovers had to be cleaned up explicitly
&lt;/h3&gt;

&lt;p&gt;Earlier experiments left behind &lt;code&gt;app.asar.unpacked&lt;/code&gt; content from previous versions.&lt;/p&gt;

&lt;p&gt;Even after the packaging approach changed, those leftovers could survive upgrades and create confusing behavior.&lt;/p&gt;

&lt;p&gt;So I added a &lt;code&gt;preinst&lt;/code&gt; cleanup step to the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"/opt/codex-desktop-native/electron/resources/app.asar.unpacked"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That made upgrades deterministic again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the build self-contained
&lt;/h2&gt;

&lt;p&gt;At first I was borrowing tools from another workspace, which worked but was brittle.&lt;/p&gt;

&lt;p&gt;I cleaned that up by making the project self-contained with local dev dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"asar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"electron"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^40.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way the repo can be built with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
./rebuild-install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of depending on some other directory on disk having the right binaries in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build flow now
&lt;/h2&gt;

&lt;p&gt;The working build pipeline looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the MSIX input.&lt;/li&gt;
&lt;li&gt;Extract the package and detect its version from &lt;code&gt;AppxManifest.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy the official assets into a staging directory.&lt;/li&gt;
&lt;li&gt;Copy in Linux Electron.&lt;/li&gt;
&lt;li&gt;Replace the original runtime shell with a generated Linux wrapper &lt;code&gt;app.asar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build a &lt;code&gt;.deb&lt;/code&gt; with the desktop entry, icon, launcher script, and package hooks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The helper script for rebuild and reinstall is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rebuild-install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;The final output is a package like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/codex-desktop-native_26.313.5234.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the user-facing launch command is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex-desktop-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most importantly: it actually opens, stays up, and works as a desktop app on Ubuntu.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from this project is that "porting" is sometimes the wrong framing.&lt;/p&gt;

&lt;p&gt;I went in thinking I needed to preserve the original Windows desktop runtime as faithfully as possible.&lt;/p&gt;

&lt;p&gt;What I really needed was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a reliable Linux package&lt;/li&gt;
&lt;li&gt;a clean desktop launcher&lt;/li&gt;
&lt;li&gt;Codex in its own app window&lt;/li&gt;
&lt;li&gt;a build process I could rerun later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I optimized for that instead of purity, the solution got much simpler.&lt;/p&gt;

&lt;p&gt;That tradeoff is worth stating directly: the final app is absolutely built from the official Windows MSIX, but it is not a perfect binary carryover of the original runtime. It is a Linux-native wrapper around the official Codex experience, using the original payload where it helps and replacing the parts that did not survive the platform jump well.&lt;/p&gt;

&lt;p&gt;That was the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;The repo is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com:johnohhh1/codex-ubuntu.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:johnohhh1/codex-ubuntu.git
&lt;span class="nb"&gt;cd &lt;/span&gt;codex-ubuntu
npm &lt;span class="nb"&gt;install&lt;/span&gt;
./rebuild-install.sh
codex-desktop-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;A lot of cross-platform packaging work looks like a debugging problem, but the real work is architectural judgment.&lt;/p&gt;

&lt;p&gt;You have to decide when to keep forcing the original design, and when to admit that a thinner, more native solution is the thing that will actually ship.&lt;/p&gt;

&lt;p&gt;This project only started working once I made that call. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a build process I could rerun later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I optimized for that instead of purity, the solution got much simpler.&lt;/p&gt;

&lt;p&gt;That tradeoff is worth stating directly: the final app is absolutely built from the official Windows MSIX, but it is not a perfect binary carryover of the original runtime. It is a Linux-native wrapper around the official Codex experience, using the original payload where it helps and replacing the parts that did not survive the platform jump well.&lt;/p&gt;

&lt;p&gt;That was the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;The repo is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com:johnohhh1/codex-ubuntu.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:johnohhh1/codex-ubuntu.git
&lt;span class="nb"&gt;cd &lt;/span&gt;codex-ubuntu
npm &lt;span class="nb"&gt;install&lt;/span&gt;
./rebuild-install.sh
codex-desktop-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;A lot of cross-platform packaging work looks like a debugging problem, but the real work is architectural judgment.&lt;/p&gt;

&lt;p&gt;You have to decide when to keep forcing the original design, and when to admit that a thinner, more native solution is the thing that will actually ship.&lt;/p&gt;

&lt;p&gt;This project only started working once I made that call.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>codex</category>
      <category>electron</category>
      <category>ported</category>
    </item>
    <item>
      <title>Run the Real ChatGPT Desktop App on Ubuntu Linux (Not a Wrapper)</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Tue, 07 Apr 2026 13:51:17 +0000</pubDate>
      <link>https://forem.com/johnohhh1/run-the-real-chatgpt-desktop-app-on-ubuntu-linux-not-a-wrapper-54g7</link>
      <guid>https://forem.com/johnohhh1/run-the-real-chatgpt-desktop-app-on-ubuntu-linux-not-a-wrapper-54g7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; OpenAI ships a Windows MSIX binary. You can unpack it, patch three platform assumptions, and run the actual official app natively on Ubuntu — same binary Windows users get. No Electron wrapper, no web view in a box.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Every Other Guide
&lt;/h2&gt;

&lt;p&gt;Search "ChatGPT desktop Ubuntu" and you'll find two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Articles pointing you at &lt;code&gt;lencx/ChatGPT&lt;/code&gt; — a third-party Electron wrapper that loads chatgpt.com in a window. It's not the real app.&lt;/li&gt;
&lt;li&gt;Articles pointing you at other wrappers doing the same thing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These work, but you're getting a community-built shell around a website — not the app itself. The real ChatGPT Desktop (the one in the Windows Store) has features, update hooks, and auth flows that wrapper apps can't replicate cleanly.&lt;/p&gt;

&lt;p&gt;This guide unpacks the official binary and runs it on Ubuntu 26.04. Tested on kernel 7.0.0, RTX 5070, NVIDIA 580/CUDA 13.0, both X11 and Wayland via XWayland.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Script Actually Does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Extracts the &lt;code&gt;x64&lt;/code&gt; MSIX from the official &lt;code&gt;.msixbundle&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pulls out the official &lt;code&gt;app.asar&lt;/code&gt; (the real app logic)&lt;/li&gt;
&lt;li&gt;Patches three platform assumptions so it boots on Linux:

&lt;ul&gt;
&lt;li&gt;Routes the platform chooser through the macOS-style implementation (Linux is close enough)&lt;/li&gt;
&lt;li&gt;Disables macOS-only &lt;code&gt;setVibrancy()&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;Skips the macOS &lt;code&gt;ioreg&lt;/code&gt; device ID path&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stages Linux Electron around the official app resources&lt;/li&gt;
&lt;li&gt;Packages everything as a proper &lt;code&gt;.deb&lt;/code&gt; — &lt;code&gt;chatgpt-desktop-native&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You end up with a system package you can install, update, and uninstall like anything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  System packages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; dpkg-dev nodejs python3 file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local Electron tooling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/chatgpt-windows-deb &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/chatgpt-windows-deb
npm &lt;span class="nb"&gt;install &lt;/span&gt;electron @electron/asar &lt;span class="nt"&gt;--no-save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Get the Official MSIX Bundle
&lt;/h2&gt;

&lt;p&gt;You need the official Windows package from OpenAI. Download &lt;code&gt;OpenAI.ChatGPT-Desktop_&amp;lt;version&amp;gt;.Msixbundle&lt;/code&gt; from the Microsoft Store or OpenAI's distribution endpoint and drop it in your working directory.&lt;/p&gt;

&lt;p&gt;The repo includes the version current at time of writing as an example payload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/chatgpt-windows-deb
git clone https://github.com/johnohhh1/chatgpt_desktop_ubuntu &lt;span class="nb"&gt;.&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;electron @electron/asar &lt;span class="nt"&gt;--no-save&lt;/span&gt;
./build-chatgpt-native-deb.sh &lt;span class="nt"&gt;--exe&lt;/span&gt; ./OpenAI.ChatGPT-Desktop_2026.212.2039.0.Msixbundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output lands in &lt;code&gt;dist/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; ./dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebuilding without a version bump? Force refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--reinstall&lt;/span&gt; ./dist/chatgpt-desktop-native_2026.212.2039.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Register Auth Callback Handlers
&lt;/h2&gt;

&lt;p&gt;The package installs a helper that registers your desktop session as the handler for the &lt;code&gt;chatgpt:&lt;/code&gt; and &lt;code&gt;chatgpt-alt:&lt;/code&gt; URL schemes (used for the login flow):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chatgpt-desktop-native-register
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it took:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xdg-mime query default x-scheme-handler/chatgpt
xdg-mime query default x-scheme-handler/chatgpt-alt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both should return &lt;code&gt;chatgpt-desktop-native.desktop&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Launch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chatgpt-desktop-native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop entry sets the WM class to &lt;code&gt;electron&lt;/code&gt; so GNOME binds the running window to the ChatGPT icon instead of the generic gear icon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Known Quirks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The terminal will print Electron/NVIDIA/VA-API noise. This is normal — ignore it.&lt;/li&gt;
&lt;li&gt;The success signal is functional login and working chat, not a clean terminal.&lt;/li&gt;
&lt;li&gt;If GNOME still shows the generic icon after first launch: close the app fully and relaunch once. Stubborn shell? Log out and back in.&lt;/li&gt;
&lt;li&gt;If OpenAI updates the Windows app significantly, the patch targets in &lt;code&gt;build-chatgpt-native-deb.sh&lt;/code&gt; may need updating. PR welcome.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reproducing on Another Machine
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo to the target machine&lt;/li&gt;
&lt;li&gt;Drop a real ChatGPT &lt;code&gt;.msix&lt;/code&gt;, &lt;code&gt;.msixbundle&lt;/code&gt;, &lt;code&gt;.appx&lt;/code&gt;, or &lt;code&gt;.appxbundle&lt;/code&gt; in the directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install electron @electron/asar --no-save&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./build-chatgpt-native-deb.sh --exe &amp;lt;your-payload&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install the generated &lt;code&gt;.deb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;chatgpt-desktop-native-register&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Launch &lt;code&gt;chatgpt-desktop-native&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/johnohhh1/chatgpt_desktop_ubuntu" rel="noopener noreferrer"&gt;github.com/johnohhh1/chatgpt_desktop_ubuntu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Issues and PRs open. If a new MSIX version breaks the patches, open an issue with the version string and I'll update the patch targets.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tested on Ubuntu 26.04 "Noble" with kernel 7.0.0-10, RTX 5070, NVIDIA driver 580, CUDA 13.0.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>chatgpt</category>
      <category>electron</category>
    </item>
  </channel>
</rss>
