<?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>DEV Community: johnohhh1</title>
    <description>The latest articles on DEV Community by johnohhh1 (@johnohhh1).</description>
    <link>https://dev.to/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%2F2f75661f-ad1a-4ed6-ab21-691b1918b382.png</url>
      <title>DEV Community: johnohhh1</title>
      <link>https://dev.to/johnohhh1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnohhh1"/>
    <language>en</language>
    <item>
      <title>I spent 6 hours debugging a VM that didn't need to exist</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Thu, 16 Apr 2026 17:07:13 +0000</pubDate>
      <link>https://dev.to/johnohhh1/i-spent-6-hours-debugging-a-vm-that-didnt-need-to-exist-549m</link>
      <guid>https://dev.to/johnohhh1/i-spent-6-hours-debugging-a-vm-that-didnt-need-to-exist-549m</guid>
      <description>&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I'm trying to get Anthropic's Cowork feature running on Linux. Cowork is officially Mac-only. It works by booting a sandboxed Ubuntu VM inside Claude Desktop, then running Claude Code inside that VM so it can safely read and write your project files.&lt;/p&gt;

&lt;p&gt;Claude Desktop for Linux exists as a community build — I maintain &lt;a href="https://github.com/johnohhh1/claude-desktop-ubuntu" rel="noopener noreferrer"&gt;a deb package&lt;/a&gt; that repackages the official Windows MSIX for Ubuntu. The desktop app works fine. But Cowork doesn't start. The VM service socket never appears.&lt;/p&gt;

&lt;p&gt;So I open Claude Code on my terminal and say: fix this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rabbit hole
&lt;/h2&gt;

&lt;p&gt;What follows is roughly 6 hours of two problem-solvers — me and Claude — locked in a feedback loop where we're both hyperfixated on the same wrong question: &lt;strong&gt;how do we make this VM boot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's an abbreviated tour of the damage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 1:&lt;/strong&gt; We extract the Electron app's &lt;code&gt;app.asar&lt;/code&gt;, find the minified JavaScript, locate the platform check. The Windows version uses a named pipe &lt;code&gt;\\.\pipe\cowork-vm-service&lt;/code&gt;. We patch it to use a Unix socket. We write a custom Node.js service that listens on that socket and spawns QEMU. Claude Desktop connects. It sends &lt;code&gt;startVM&lt;/code&gt;. QEMU boots. The guest agent connects. Progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 2:&lt;/strong&gt; The &lt;code&gt;installSdk&lt;/code&gt; call loops forever. We make it return success locally. Startup completes in 2.5 seconds. Then &lt;code&gt;isProcessRunning&lt;/code&gt; keepalives timeout because the guest agent isn't responding. We discover the guest's &lt;code&gt;sdk-daemon&lt;/code&gt; binary uses vsock, not the virtio serial port we set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 3:&lt;/strong&gt; The &lt;code&gt;sdk-daemon&lt;/code&gt; inside the VM can't find the "smol-bin" disk. It scans for NVMe devices. QEMU's NVMe emulation produces "bogus Namespace Identifiers" that the Ubuntu 22.04 kernel rejects. &lt;code&gt;nvme1n1&lt;/code&gt; never appears as a block device. We try &lt;code&gt;uuid=auto&lt;/code&gt;, &lt;code&gt;nguid=auto&lt;/code&gt;, &lt;code&gt;eui64-default=on&lt;/code&gt;. None of it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 4:&lt;/strong&gt; We try SCSI instead of NVMe. The device appears as &lt;code&gt;/dev/sda&lt;/code&gt; but the sdk-daemon only scans NVMe paths. We try virtio-blk. Same. We discover the "smol-bin" disk is actually just an empty ext4 sessions disk, not the binary distribution we thought. We patch the rootfs with a fake tmpfs mount. The sdk-daemon gets past the smol-bin check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 5:&lt;/strong&gt; New error: VirtioFS mount fails. We add &lt;code&gt;virtiofsd&lt;/code&gt; and &lt;code&gt;memory-backend-memfd&lt;/code&gt; to QEMU. The sdk-daemon gets further. It mounts the sessions disk. It starts a MITM proxy. Then: &lt;code&gt;[rpc] connecting to host CID=2 port=51234&lt;/code&gt; — connection refused. We write a Python vsock bridge. It connects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 6:&lt;/strong&gt; The bridge works but the sdk-daemon crashes on restart because the fake mounts get torn down. We patch the rootfs systemd units. We patch the wrapper script. We fight with rootfs journal persistence. We're deep in the weeds of a QEMU VM running Ubuntu 22.04 inside Ubuntu 26.04, bridging vsock through Python to Node.js through Unix sockets to Electron, trying to make a Go binary that was compiled for Hyper-V happy inside KVM.&lt;/p&gt;

&lt;p&gt;Then I find &lt;a href="https://github.com/johnzfitch/claude-cowork-linux" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was 3 lines of thinking
&lt;/h2&gt;

&lt;p&gt;The entire README of &lt;code&gt;claude-cowork-linux&lt;/code&gt; by &lt;a href="https://github.com/johnzfitch" rel="noopener noreferrer"&gt;@johnzfitch&lt;/a&gt; can be summarized as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why does Cowork use a VM?&lt;/strong&gt; Because it needs a Linux environment to run Claude Code. On macOS, that means spinning up an Ubuntu VM. On Windows, same thing — Hyper-V boots a Linux guest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But you're already on Linux.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So just... don't boot the VM. Stub out the macOS native module. Run Claude Code directly on the host. Translate the VM paths to real paths. Done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The install took 30 seconds. Cowork started immediately. The spawned Claude Code process ran for 23ms to confirm, then the full session worked.&lt;/p&gt;

&lt;p&gt;Every single thing I spent 6 hours debugging — NVMe namespace identifiers, vsock port discovery, VirtioFS permissions, rootfs systemd patches, Python-to-Node socket bridges — was a consequence of not asking &lt;strong&gt;why&lt;/strong&gt; the VM existed in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;This is a pattern I've seen before but never this clearly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hyperfixation on the error, not the cause.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The error said "VM service not running." So we made the VM service run. Then the error said "guest agent not connected." So we connected the guest agent. Then "smol-bin device not found." So we tried to make the device appear. Every error led to the next error, and we solved each one, and we were making "progress" the entire time.&lt;/p&gt;

&lt;p&gt;But the actual cause was upstream of all of it: &lt;strong&gt;the VM shouldn't exist on Linux in the first place.&lt;/strong&gt; We never questioned the premise. We just tried to make the wrong thing work really, really well.&lt;/p&gt;

&lt;p&gt;And here's the uncomfortable part: Claude Code was the perfect partner for this kind of mistake. It's incredible at "make X work" tasks. Give it an error, it'll fix it. Give it the next error, it'll fix that too. It will reverse-engineer a binary's vsock port by reading Go strings out of an ELF executable at 2 AM and not complain. It's a relentless debugger.&lt;/p&gt;

&lt;p&gt;But relentless debugging is exactly what you don't need when the whole approach is wrong. What you need is someone to say: &lt;em&gt;wait, why are we doing this?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I should have done
&lt;/h2&gt;

&lt;p&gt;Before touching any code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask what the VM is for.&lt;/strong&gt; It runs Linux so Claude Code has a sandbox. We're on Linux. We don't need the sandbox VM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search for prior art.&lt;/strong&gt; Someone already solved this. 30 seconds of GitHub search would have found it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Question the frame.&lt;/strong&gt; The logs said "VM service not running" and I read that as "I need to make the VM service run." A better read: "this component assumes a VM is needed — is it?"&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;AI coding assistants are force multipliers. If you're pointed in the right direction, they're unbelievable. If you're pointed in the wrong direction, they'll help you dig the most elaborate, well-engineered hole you've ever seen.&lt;/p&gt;

&lt;p&gt;The fix for Cowork on Linux was a JavaScript stub and a path translation layer. We built a QEMU VM orchestrator with NVMe disk emulation, vsock bridging, VirtioFS shared filesystems, and rootfs systemd patching.&lt;/p&gt;

&lt;p&gt;Next time the debugging gets complicated, I'm going to stop and ask: &lt;strong&gt;am I solving the right problem, or am I just solving the next error?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Credit to &lt;a href="https://github.com/johnzfitch" rel="noopener noreferrer"&gt;@johnzfitch&lt;/a&gt; for &lt;a href="https://github.com/johnzfitch/claude-cowork-linux" rel="noopener noreferrer"&gt;claude-cowork-linux&lt;/a&gt;, which solved in an afternoon what I couldn't solve in a night. The deb package with Cowork support is at &lt;a href="https://github.com/johnohhh1/claude-desktop-ubuntu" rel="noopener noreferrer"&gt;johnohhh1/claude-desktop-ubuntu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>debugging</category>
      <category>ai</category>
      <category>claude</category>
    </item>
    <item>
      <title>I Ported ComfyUI Desktop to Ubuntu 26.04</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Wed, 15 Apr 2026 03:06:51 +0000</pubDate>
      <link>https://dev.to/johnohhh1/i-ported-comfyui-desktop-to-ubuntu-2604-2cb8</link>
      <guid>https://dev.to/johnohhh1/i-ported-comfyui-desktop-to-ubuntu-2604-2cb8</guid>
      <description>&lt;h2&gt;
  
  
  Another Desktop App ported to Linux That was not available. I will Keep going until release day or I run out of apps to port. Let me Know what one you would like to see next!
&lt;/h2&gt;

&lt;p&gt;The official ComfyUI Desktop app ships for Windows and macOS.&lt;/p&gt;

&lt;p&gt;I wanted it on Ubuntu 26.04.&lt;/p&gt;

&lt;p&gt;So I ported it.&lt;/p&gt;

&lt;p&gt;Not a web wrapper. Not a fake launcher. Not a shell script around a browser tab.&lt;/p&gt;

&lt;p&gt;The actual desktop app.&lt;/p&gt;

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

&lt;p&gt;I had two requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build the real app on Linux&lt;/li&gt;
&lt;li&gt;make it usable on a machine that already runs ComfyUI normally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That second one mattered more than people think.&lt;/p&gt;

&lt;p&gt;If a Linux user already has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real &lt;code&gt;~/ComfyUI&lt;/code&gt; install&lt;/li&gt;
&lt;li&gt;a running server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the desktop app should not pretend the machine is blank and try to push a fresh install into &lt;code&gt;~/Documents/ComfyUI&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is bad product behavior, even if the package technically launches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

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

&lt;p&gt;&lt;a href="https://github.com/Comfy-Org/desktop" rel="noopener noreferrer"&gt;https://github.com/Comfy-Org/desktop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance it looks cross-platform enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Electron app&lt;/li&gt;
&lt;li&gt;bundled frontend assets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv&lt;/code&gt; bootstrap&lt;/li&gt;
&lt;li&gt;asset build pipeline&lt;/li&gt;
&lt;li&gt;electron-builder config with Linux bits already half-present&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But “half-present” is the dangerous state.&lt;/p&gt;

&lt;p&gt;That usually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;some paths exist&lt;/li&gt;
&lt;li&gt;some assets exist&lt;/li&gt;
&lt;li&gt;some packaging config exists&lt;/li&gt;
&lt;li&gt;and the app still does not actually ship on Linux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which is exactly what was going on here.&lt;/p&gt;

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

&lt;p&gt;There were a few distinct failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Linux was gated out of asset/bootstrap flow
&lt;/h3&gt;

&lt;p&gt;The app would build fine for supported platforms, but Linux was not being carried all the way through asset setup and build verification.&lt;/p&gt;

&lt;p&gt;That meant some Linux artifacts were never properly prepared even though the source tree looked close.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Packaged builds were missing &lt;code&gt;desktop-ui&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This was the first hard packaging bug.&lt;/p&gt;

&lt;p&gt;The app launched, then died with a file-not-found error because the packaged build did not contain the desktop onboarding UI bundle.&lt;/p&gt;

&lt;p&gt;That is the kind of bug that makes a port look dead before you even get to runtime behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AppImage hit Electron sandbox issues
&lt;/h3&gt;

&lt;p&gt;This is the standard Linux Electron tax.&lt;/p&gt;

&lt;p&gt;The AppImage mounted fine, but Chromium tripped over the sandbox helper and aborted before the app could become useful.&lt;/p&gt;

&lt;p&gt;I did not waste time pretending AppImage was the right hill to die on.&lt;/p&gt;

&lt;p&gt;For Ubuntu, &lt;code&gt;.deb&lt;/code&gt; was the better target.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Linux runtime assumptions were wrong
&lt;/h3&gt;

&lt;p&gt;The app still had platform checks and install behavior that were effectively “Windows/macOS first, Linux later.”&lt;/p&gt;

&lt;p&gt;A Linux port cannot just compile. It has to stop acting like Linux is unsupported at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Existing Comfy installs were ignored
&lt;/h3&gt;

&lt;p&gt;This was the most important UX issue.&lt;/p&gt;

&lt;p&gt;On my machine, ComfyUI was already installed and already running in the browser.&lt;/p&gt;

&lt;p&gt;The desktop app still tried to start from scratch and push a managed install path into &lt;code&gt;Documents&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is technically launchable, but functionally wrong.&lt;/p&gt;

&lt;p&gt;So I fixed it.&lt;/p&gt;

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

&lt;p&gt;I split the work into build fixes, runtime fixes, and “make it behave like a sane Linux app” fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build and packaging fixes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;enabled Linux asset bootstrap&lt;/li&gt;
&lt;li&gt;enabled Linux build verification&lt;/li&gt;
&lt;li&gt;packaged the missing &lt;code&gt;desktop-ui&lt;/code&gt; bundle&lt;/li&gt;
&lt;li&gt;switched the Linux target to &lt;code&gt;.deb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Runtime fixes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;added Linux config/reset path handling&lt;/li&gt;
&lt;li&gt;made Linux hardware validation non-fatal&lt;/li&gt;
&lt;li&gt;added fallback behavior for missing compiled requirements&lt;/li&gt;
&lt;li&gt;added Linux-safe Electron startup defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Existing-install behavior
&lt;/h3&gt;

&lt;p&gt;This was the good part.&lt;/p&gt;

&lt;p&gt;I changed the app so it now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prefers an existing &lt;code&gt;~/ComfyUI&lt;/code&gt; directory as the default base path&lt;/li&gt;
&lt;li&gt;detects a live local Comfy server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;persists external-server mode into desktop config&lt;/li&gt;
&lt;li&gt;skips the desktop-managed venv validation path when attaching to that external server&lt;/li&gt;
&lt;li&gt;opens the frontend against the already-running instance instead of forcing first-run install flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last bit is what makes the port feel real instead of merely “bootable.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;.deb&lt;/code&gt; Won
&lt;/h2&gt;

&lt;p&gt;I know someone will ask why I did not just ship AppImage.&lt;/p&gt;

&lt;p&gt;Because Ubuntu users need something that works, not something ideologically pure.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.deb&lt;/code&gt; route gave me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable installation under &lt;code&gt;/opt/ComfyUI&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;normal desktop integration&lt;/li&gt;
&lt;li&gt;no AppImage sandbox headache&lt;/li&gt;
&lt;li&gt;a clean launcher path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Linux, packaging format is not theology. It is operational reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Does Now
&lt;/h2&gt;

&lt;p&gt;On an Ubuntu 26.04 machine with an existing Comfy setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;launch the desktop app&lt;/li&gt;
&lt;li&gt;it detects &lt;code&gt;~/ComfyUI&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;it sees the live server on &lt;code&gt;127.0.0.1:8188&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;it stores that as the desktop install state&lt;/li&gt;
&lt;li&gt;it attaches instead of onboarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the behavior I wanted from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands I Actually Used
&lt;/h2&gt;

&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;corepack &lt;span class="nb"&gt;enable
&lt;/span&gt;corepack yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap app assets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;corepack yarn make:assets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;corepack yarn typecheck
corepack yarn vitest run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build 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;corepack yarn make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install 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;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; ./dist/ComfyUI-0.8.30-amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Nice Surprise
&lt;/h2&gt;

&lt;p&gt;The upstream repo was not hopeless.&lt;/p&gt;

&lt;p&gt;This was not one of those situations where “Linux support” secretly meant rewriting the app from scratch.&lt;/p&gt;

&lt;p&gt;Most of the work was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;finishing incomplete Linux paths&lt;/li&gt;
&lt;li&gt;fixing packaging omissions&lt;/li&gt;
&lt;li&gt;picking the right distribution target&lt;/li&gt;
&lt;li&gt;making startup logic respect an existing local Comfy workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is still real work, but it is not fantasy.&lt;/p&gt;

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

&lt;p&gt;A port is not finished when the window appears.&lt;/p&gt;

&lt;p&gt;A port is finished when the app behaves correctly on the target machine.&lt;/p&gt;

&lt;p&gt;For this one, the difference was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not just “ComfyUI Desktop launches on Ubuntu”&lt;/li&gt;
&lt;li&gt;but “ComfyUI Desktop understands an Ubuntu user who already runs ComfyUI”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the difference between a demo and a port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upstream and Source
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upstream desktop app: &lt;a href="https://github.com/Comfy-Org/desktop" rel="noopener noreferrer"&gt;https://github.com/Comfy-Org/desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ComfyUI core: &lt;a href="https://github.com/comfyanonymous/ComfyUI" rel="noopener noreferrer"&gt;https://github.com/comfyanonymous/ComfyUI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://github.com/johnohhh1/comfyui-desktop-port-linux.git" rel="noopener noreferrer"&gt;https://github.com/johnohhh1/comfyui-desktop-port-linux.git&lt;/a&gt;&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 https://github.com/johnohhh1/comfyui-desktop-port-linux.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>electron</category>
      <category>ai</category>
    </item>
    <item>
      <title>Kimi Desktop on Ubuntu 26.04: Fixing the Broken .deb with Tauri v2</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Tue, 14 Apr 2026 02:54:43 +0000</pubDate>
      <link>https://dev.to/johnohhh1/kimi-desktop-on-ubuntu-2604-fixing-the-broken-deb-with-tauri-v2-1e25</link>
      <guid>https://dev.to/johnohhh1/kimi-desktop-on-ubuntu-2604-fixing-the-broken-deb-with-tauri-v2-1e25</guid>
      <description>&lt;p&gt;You install the official Kimi desktop &lt;code&gt;.deb&lt;/code&gt;, fire &lt;code&gt;sudo dpkg -i&lt;/code&gt;, and boom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dpkg: dependency problems prevent configuration of kimi:
 kimi depends on libwebkit2gtk-4.0-37; however:
  Package libwebkit2gtk-4.0-37 is not installed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That library doesn't exist on Ubuntu 24.04, let alone 26.04. It was removed from the repos over a year ago. The official &lt;a href="https://github.com/kimi-moonshot/kimi-moonshot" rel="noopener noreferrer"&gt;Kimi desktop package&lt;/a&gt; is built on Tauri v1, which hard-depends on &lt;code&gt;libwebkit2gtk-4.0.so.37&lt;/code&gt; — a library that shipped with webkit2gtk 4.0, superseded by 4.1 and then dropped entirely.&lt;/p&gt;

&lt;p&gt;So the app is just... broken on any modern Ubuntu. Here's how I fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem in one sentence
&lt;/h2&gt;

&lt;p&gt;Tauri v1 → &lt;code&gt;libwebkit2gtk-4.0&lt;/code&gt; → removed from Ubuntu 24.04+ → &lt;code&gt;dpkg&lt;/code&gt; fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: rebuild with Tauri v2
&lt;/h2&gt;

&lt;p&gt;Tauri v2 links against &lt;code&gt;libwebkit2gtk-4.1&lt;/code&gt;, which &lt;em&gt;is&lt;/em&gt; the version shipped in Ubuntu 24.04 and 26.04. So the fix is straightforward: rebuild the app with Tauri v2 instead of v1.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://github.com/tw93/Pake" rel="noopener noreferrer"&gt;Pake&lt;/a&gt; v3, which wraps any web app into a native desktop app using Tauri under the hood. One build script, one config file, and you get a &lt;code&gt;.deb&lt;/code&gt; that actually installs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tauri v2 runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Links against &lt;code&gt;libwebkit2gtk-4.1&lt;/code&gt; — the one Ubuntu actually ships&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OAuth / SSO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--new-window&lt;/code&gt; flag means Google sign-in works in-app instead of being blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System tray&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Desktop integration that works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1200x780 window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Matches the original Kimi desktop dimensions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Rebuild it yourself
&lt;/h2&gt;

&lt;p&gt;Prerequisites — Rust, Node, and the usual GTK/webkit dev packages:&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="c"&gt;# Rust &amp;gt;= 1.85&lt;/span&gt;
curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://sh.rustup.rs | sh

&lt;span class="c"&gt;# Node.js &amp;gt;= 22 — use nvm, brew, whatever you prefer&lt;/span&gt;

&lt;span class="c"&gt;# Build deps&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libwebkit2gtk-4.1-dev libgtk-3-dev &lt;span class="se"&gt;\&lt;/span&gt;
  libayatana-appindicator3-dev librsvg2-dev

&lt;span class="c"&gt;# Pake CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pake-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it's one command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.deb&lt;/code&gt; lands in &lt;code&gt;dist/&lt;/code&gt;. Install it:&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;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; dist/kimi_1.0.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Kimi runs natively on Ubuntu 26.04 with no missing libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The config that makes it work
&lt;/h2&gt;

&lt;p&gt;Everything lives in &lt;code&gt;config/pake.json&lt;/code&gt;. The important bits:&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;"windows"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://kimi.moonshot.cn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"new_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;780&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;"user_agent"&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;"linux"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"&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;The two things that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;new_window: true&lt;/code&gt;&lt;/strong&gt; — Without this, OAuth popups (Google sign-in, etc.) get blocked by the webview's navigation policy. This flag tells Pake/Tauri to open them in a new window instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;user_agent.linux&lt;/code&gt;&lt;/strong&gt; — Spoofing a Chrome UA because some OAuth providers reject webview user agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why not just use the web app in a browser?
&lt;/h2&gt;

&lt;p&gt;Fair question. A native desktop app gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alt-Tab separation&lt;/strong&gt; — Kimi isn't buried among 40 browser tabs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System tray&lt;/strong&gt; — Quick access, stays running in the background&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Own window chrome&lt;/strong&gt; — Feels like an app, not a tab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller memory footprint&lt;/strong&gt; — Tauri uses the system webview, not a bundled Electron instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The repo
&lt;/h2&gt;

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

&lt;p&gt;Clone it, build it, install it. If you're on Ubuntu 24.04+ and want Kimi as a desktop app, this is currently the only way that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uninstall
&lt;/h2&gt;

&lt;p&gt;If you need to remove it:&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;dpkg &lt;span class="nt"&gt;-r&lt;/span&gt; kimi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Kimi is a product of &lt;a href="https://moonshot.cn" rel="noopener noreferrer"&gt;Moonshot AI&lt;/a&gt;. This project uses the open-source &lt;a href="https://github.com/tw93/Pake" rel="noopener noreferrer"&gt;Pake&lt;/a&gt; tool (MIT license) to wrap the Kimi web interface as a native desktop application.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ubuntu</category>
      <category>tauri</category>
      <category>desktop</category>
    </item>
    <item>
      <title>I Ported the Ollama Desktop App to Linux Just in Time for Ubuntu 26.04 LTS</title>
      <dc:creator>johnohhh1</dc:creator>
      <pubDate>Sat, 11 Apr 2026 22:46:54 +0000</pubDate>
      <link>https://dev.to/johnohhh1/i-ported-the-ollama-desktop-app-to-linux-just-in-time-for-ubuntu-2604-lts-4bnp</link>
      <guid>https://dev.to/johnohhh1/i-ported-the-ollama-desktop-app-to-linux-just-in-time-for-ubuntu-2604-lts-4bnp</guid>
      <description>&lt;p&gt;Ubuntu 26.04 LTS drops this week. I've been running the RC for months. And I think it's going to be the release that finally pulls a meaningful chunk of people off Windows — especially AI-curious developers who are tired of the telemetry, the forced updates, and the general feeling that Microsoft is treating them like a product.&lt;/p&gt;

&lt;p&gt;So I've been on a mission: take the cool AI tooling that still assumes Windows or macOS, and make it work natively on Linux.&lt;/p&gt;

&lt;p&gt;This week's project: &lt;strong&gt;the official Ollama desktop app&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; is incredible. Local LLMs, dead simple, fast. But their desktop app — the polished GTK-style chat UI with model management, file attachments, settings, chat history — &lt;strong&gt;officially ships for Windows and macOS only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every single Go file in the &lt;code&gt;app/&lt;/code&gt; directory of &lt;a href="https://github.com/ollama/ollama" rel="noopener noreferrer"&gt;ollama/ollama&lt;/a&gt; starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:build windows || darwin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux users are left running &lt;code&gt;ollama serve&lt;/code&gt; and pointing a browser at &lt;code&gt;localhost:11434&lt;/code&gt;. It works, but it's not the experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Discovery
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, I did what any good engineer does: I read the source.&lt;/p&gt;

&lt;p&gt;The Ollama desktop app is built on &lt;a href="https://github.com/webview/webview" rel="noopener noreferrer"&gt;webview&lt;/a&gt; — a lightweight C++ library that wraps the platform's native browser engine. On Windows it uses WebView2 (Edge). On macOS it uses WebKit. And on Linux?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From webview.h, line ~400:&lt;/span&gt;
&lt;span class="cp"&gt;#if defined(WEBVIEW_GTK)
&lt;/span&gt;&lt;span class="c1"&gt;// Full GTK3 + WebKit2GTK implementation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Linux support was already there.&lt;/strong&gt; Fully implemented. Sitting in the header file, waiting. The Go build tags were the only thing blocking it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Port
&lt;/h2&gt;

&lt;p&gt;Six new files. A patch. About 350 lines of Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Unlock the build tags
&lt;/h3&gt;

&lt;p&gt;~40 files needed one line changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// before&lt;/span&gt;
&lt;span class="c"&gt;//go:build windows || darwin&lt;/span&gt;

&lt;span class="c"&gt;// after  &lt;/span&gt;
&lt;span class="c"&gt;//go:build windows || darwin || linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Wire up the CGo flags
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;app/webview/webview.go&lt;/code&gt; needed Linux-specific build flags to find GTK and WebKit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;cgo&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt; &lt;span class="n"&gt;CXXFLAGS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;DWEBVIEW_GTK&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;cgo&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt; &lt;span class="n"&gt;LDFLAGS&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ldl&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;cgo&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gtk&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="m"&gt;3.0&lt;/span&gt; &lt;span class="n"&gt;webkit2gtk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;4.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Ubuntu 26.04 ships &lt;code&gt;webkit2gtk-4.1&lt;/code&gt; — not &lt;code&gt;4.0&lt;/code&gt;. If you're targeting older distros, check your version.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Platform files
&lt;/h3&gt;

&lt;p&gt;The app architecture expects platform-specific implementations of a handful of interfaces. I wrote six &lt;code&gt;_linux.go&lt;/code&gt; files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;app_linux.go&lt;/code&gt;&lt;/strong&gt; — GTK window management via CGo, lockfile-based single-instance enforcement, signal cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleExistingInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startHidden&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lockPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_EXCL&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_WRONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;o644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"another ollama-app instance detected, exiting"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGHUP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
        &lt;span class="n"&gt;removeLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;server_linux.go&lt;/code&gt;&lt;/strong&gt; — The app normally spawns the Ollama daemon itself. On Linux, systemd handles that. So this file is just a health-checker that pings &lt;code&gt;localhost:11434&lt;/code&gt; every 30 seconds and logs if it's unreachable. Never spawns a process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;updater_linux.go&lt;/code&gt;&lt;/strong&gt; — All no-ops. systemd and apt manage updates on Linux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;filepicker_linux.go&lt;/code&gt;&lt;/strong&gt; — File/folder dialogs via &lt;code&gt;zenity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;selectDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zenity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--file-selection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--directory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--title="&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;dialog/dlgs_linux.go&lt;/code&gt;&lt;/strong&gt; — zenity implementations of the app's dialog interface (yes/no, info, error, file pickers).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;webview_linux.go&lt;/code&gt;&lt;/strong&gt; — The Webview struct without the Windows &lt;code&gt;menu.h&lt;/code&gt; CGo baggage, plus all the JS bindings: &lt;code&gt;ready&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;drag&lt;/code&gt;, &lt;code&gt;doubleClick&lt;/code&gt;, &lt;code&gt;zoomIn&lt;/code&gt;, &lt;code&gt;zoomOut&lt;/code&gt;, &lt;code&gt;selectFiles&lt;/code&gt;, &lt;code&gt;selectWorkingDirectory&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The one frontend bug
&lt;/h3&gt;

&lt;p&gt;The React sidebar had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isWindows&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux, &lt;code&gt;navigator.platform&lt;/code&gt; returns &lt;code&gt;"Linux x86_64"&lt;/code&gt; — so &lt;code&gt;isWindows&lt;/code&gt; is false and Settings just... doesn't appear. One-line fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mac&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isMac&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Settings&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/johnohhh1/ollama-webchat-ubuntu.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ollama-webchat-ubuntu
bash build.sh
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;build/ollama-app /usr/local/bin/ollama-app
ollama-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A native GTK3 window. WebKit2GTK rendering the full React SPA. Chat history in SQLite. File attachments. Model management. Settings. Dark mode. Wayland-native — no XWayland needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The NVIDIA gotcha
&lt;/h2&gt;

&lt;p&gt;Running an RTX 5070 (Blackwell) with the proprietary driver? You'll see a wall of EGL warnings on startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 17: 10de:2f04, driver (null)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ignore them.&lt;/strong&gt; There's no open-source Mesa driver for Blackwell yet — WebKit probes Mesa EGL, fails, and falls back to Cairo software rendering for the compositor. For a chat UI it's completely imperceptible. Page layout and text were always CPU-rendered anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Ubuntu 26.04 changes things
&lt;/h2&gt;

&lt;p&gt;26.04 LTS is the first Ubuntu release with X11 removed entirely — Wayland only. GNOME 50. The desktop finally feels like a first-class target, not an afterthought.&lt;/p&gt;

&lt;p&gt;At the same time, the local AI tooling ecosystem has exploded. Ollama, LM Studio, Open WebUI, llama.cpp — these tools are genuinely good, and they mostly work on Linux. The desktop app experience is just lagging behind.&lt;/p&gt;

&lt;p&gt;That's the gap I'm trying to close. If you're a developer making the jump from Windows to Ubuntu 26.04 this week, you shouldn't have to give up your tools.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/johnohhh1/ollama-webchat-ubuntu" rel="noopener noreferrer"&gt;github.com/johnohhh1/ollama-webchat-ubuntu&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PRs welcome — especially if you test on Fedora, Arch, or Ubuntu 24.04. And if you're building other AI tools that are stuck behind Windows/macOS build tags, the pattern here is reusable.&lt;/p&gt;

&lt;p&gt;The C library usually already supports Linux. The build tags are almost always the only thing in the way.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>ubuntu</category>
      <category>linux</category>
      <category>go</category>
    </item>
    <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://dev.to/johnohhh1/anthropic-limited-claude-subscription-usage-for-third-party-harnesses-so-we-ported-nanoclaw-to-47ic</link>
      <guid>https://dev.to/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://dev.to/johnohhh1/how-i-repackaged-the-official-windows-codex-msix-into-a-working-linux-deb-48ch</link>
      <guid>https://dev.to/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://dev.to/johnohhh1/run-the-real-chatgpt-desktop-app-on-ubuntu-linux-not-a-wrapper-54g7</link>
      <guid>https://dev.to/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>
