<?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: Karim Masmoudi</title>
    <description>The latest articles on DEV Community by Karim Masmoudi (@kernelhunter).</description>
    <link>https://dev.to/kernelhunter</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4131947%2F330e7c02-5ab1-41f5-99ea-506d6014a9ad.jpg</url>
      <title>DEV Community: Karim Masmoudi</title>
      <link>https://dev.to/kernelhunter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kernelhunter"/>
    <language>en</language>
    <item>
      <title>Shipping a Swift CLI on Windows: what broke</title>
      <dc:creator>Karim Masmoudi</dc:creator>
      <pubDate>Sun, 11 Oct 2026 17:01:29 +0000</pubDate>
      <link>https://dev.to/kernelhunter/shipping-a-swift-cli-on-windows-what-broke-513a</link>
      <guid>https://dev.to/kernelhunter/shipping-a-swift-cli-on-windows-what-broke-513a</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Kernel-Hunter/mudroom" rel="noopener noreferrer"&gt;Mudroom&lt;/a&gt; runs coding agents like Claude Code, Codex or Gemini CLI in a Linux container, on a copy of your project, and lets you review their changes like a pull request before anything touches your real files. It started as a Mac app. Version 0.2.0 adds a Windows build of the CLI, for x64 and arm64, using Docker Desktop or Podman as the sandbox.&lt;/p&gt;

&lt;p&gt;Swift on Windows works better than I expected. Most of the code compiled once the platform-specific parts were split out. What took the time was everything around the compiler: linking, sockets, the file system, PowerShell and CI. Here's what broke, in case you're about to do the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The C++ headers refuse the compiler
&lt;/h2&gt;

&lt;p&gt;The first build failed before compiling a single Swift file. Swift 6.2 ships Clang 19, and the Visual Studio 2026 C++ headers on GitHub's Windows runners refuse anything older than Clang 20. The headers have an escape hatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;swift build &lt;span class="nt"&gt;-Xcxx&lt;/span&gt; &lt;span class="nt"&gt;-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's in every Windows build command in CI now, and in the README for anyone building from source.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. There's no static standard library
&lt;/h2&gt;

&lt;p&gt;On Linux you can pass &lt;code&gt;--static-swift-stdlib&lt;/code&gt; and get one binary. On Windows that flag builds, but the exe still imports &lt;code&gt;swiftCore.dll&lt;/code&gt; and friends. &lt;code&gt;-Xswiftc -static-stdlib&lt;/code&gt; fails outright with "unable to load standard library", because the Windows SDK ships no static Swift libraries.&lt;/p&gt;

&lt;p&gt;So the release zip carries the DLLs. The packaging script doesn't use a hand-written list. It runs &lt;code&gt;dumpbin /dependents&lt;/code&gt; on the exe, then on every DLL it finds, until nothing new shows up. That gives 15 DLLs on both architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Swift runtime: swiftCore, swiftCRT, swiftWinSDK, swift_Concurrency, swift_StringProcessing, swift_RegexParser, swiftDispatch, dispatch, BlocksRuntime&lt;/li&gt;
&lt;li&gt;Foundation: Foundation, FoundationEssentials, FoundationInternationalization and _FoundationICU (the ICU data alone is about 36 MB unpacked)&lt;/li&gt;
&lt;li&gt;the Visual C++ runtime: VCRUNTIME140 and MSVCP140&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The zip ends up around 29 MB. To check that it really runs without Swift installed, CI unzips it on a separate runner that never had a Swift toolchain. It confirms there's no &lt;code&gt;swift&lt;/code&gt; on PATH, runs the full smoke test, then renames &lt;code&gt;swiftCore.dll&lt;/code&gt; and checks that the exe now fails to start with 0xC0000135 (DLL not found). Without that last step, a stray Swift install on the runner could make the test pass for the wrong reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A socket is not a file descriptor
&lt;/h2&gt;

&lt;p&gt;On macOS and Linux a socket is a file descriptor, and &lt;code&gt;close()&lt;/code&gt; closes it. On Windows a socket is a &lt;code&gt;SOCKET&lt;/code&gt; handle from Winsock, and handing it to the C runtime's &lt;code&gt;close()&lt;/code&gt; crashes the process. Two tests did exactly that. The fix is a small &lt;code&gt;Sock.close&lt;/code&gt; that calls &lt;code&gt;closesocket&lt;/code&gt; on Windows, and using it everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The 503 that never arrived
&lt;/h2&gt;

&lt;p&gt;Mudroom's network proxy caps how many connections it serves. Over the cap, it writes a 503 and closes the connection. On Windows the client never saw the 503.&lt;/p&gt;

&lt;p&gt;If you close a TCP socket while the peer's request is still unread in your receive buffer, Windows sends a reset instead of a normal close, and the reset throws away the 503 the client hasn't read yet. On Windows the proxy now shuts down its sending side, then reads and discards the request for up to 250 ms, so the client gets the 503 before the connection goes away.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Atomic writes meet antivirus
&lt;/h2&gt;

&lt;p&gt;Session state is written to a temp file, then moved over the old one with &lt;code&gt;MoveFileExW(..., MOVEFILE_REPLACE_EXISTING)&lt;/code&gt;. That failed now and then with a sharing violation. Something else had the file open for a moment: Defender scanning the new file, or the search indexer.&lt;/p&gt;

&lt;p&gt;The fix is boring. On ERROR_SHARING_VIOLATION or ERROR_LOCK_VIOLATION, wait 50 ms and try again, up to ten times. macOS and Linux keep Foundation's plain &lt;code&gt;.atomic&lt;/code&gt; write.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Files don't work the way Unix code expects
&lt;/h2&gt;

&lt;p&gt;A diff tool cares about file metadata, and Windows has different metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are no mode bits, so every file reads as 0644. That means &lt;code&gt;chmod +x&lt;/code&gt; inside the sandbox doesn't show up in the diff on Windows. It's documented, and nothing to fix.&lt;/li&gt;
&lt;li&gt;A read-only attribute makes the replace fail with access denied. The applier clears the attribute and tries again (and puts it back if the retry fails too). Undo restores the old file.&lt;/li&gt;
&lt;li&gt;Junctions (Windows folder links) are reported in the review but never followed, the same way symlinks are handled elsewhere.&lt;/li&gt;
&lt;li&gt;Long paths: every path handed to the wide-character Win32 calls gets the &lt;code&gt;\\?\&lt;/code&gt; prefix once it nears the old 260-character limit.&lt;/li&gt;
&lt;li&gt;CRLF line endings have to survive the round trip through the Linux container untouched. A &lt;code&gt;.gitattributes&lt;/code&gt; also keeps the agent image files and shell scripts on LF, so Git's checkout on the runner doesn't rewrite them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. PowerShell eats &lt;code&gt;--&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The smoke test runs &lt;code&gt;mudroom new &amp;lt;project&amp;gt; --agent smoke -- cmd /c exit&lt;/code&gt;. When that line went through a PowerShell helper function, the bare &lt;code&gt;--&lt;/code&gt; vanished. PowerShell treats &lt;code&gt;--&lt;/code&gt; as its own end-of-parameters marker. Mudroom then saw &lt;code&gt;cmd /c exit&lt;/code&gt; as stray arguments. The fix is to build the arguments as an array and pass the array, so PowerShell doesn't parse them.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. What CI can and can't test
&lt;/h2&gt;

&lt;p&gt;GitHub's Windows runners can't run Linux containers by default, so for a while nothing tested a real sandbox. It turns out &lt;code&gt;windows-latest&lt;/code&gt; runs on hardware with nested virtualization, so WSL 2 and a Podman machine work there. CI now builds the agent image with Podman, runs the network check and a short real session, then diffs and applies the result.&lt;/p&gt;

&lt;p&gt;Two things are still open, and I'd rather say so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Locked network mode on Windows.&lt;/strong&gt; That's the default, where the only way out of the sandbox is an allowlist proxy on the host. It hasn't passed in CI yet: on the runner's rootless Podman machine, the network check hangs. The open-network mode works. I'm working on a fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Desktop on a real PC.&lt;/strong&gt; It hasn't been tested yet. The arm64 runner can't run a VM at all, so on arm64 only the zip and the smoke test run in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One Podman detail for anyone else trying this: the runner's Podman machine had no &lt;code&gt;pids&lt;/code&gt; cgroup controller, and containers failed with "controller &lt;code&gt;pids&lt;/code&gt; is not available". Setting &lt;code&gt;pids_limit = 0&lt;/code&gt; in the machine's containers.conf fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;scoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kernel-hunter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/Kernel-Hunter/scoop-bucket&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;scoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mudroom&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also plain zips on the &lt;a href="https://github.com/Kernel-Hunter/mudroom/releases/tag/v0.2.0" rel="noopener noreferrer"&gt;release page&lt;/a&gt;, and a winget package is in review. You'll need Docker Desktop or Podman. If you run it on a real Windows machine, especially with Docker Desktop, I'd like to hear how it went, good or bad: &lt;a href="https://github.com/Kernel-Hunter/mudroom/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>windows</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>Why my Mac memory app doesn't try to empty your RAM</title>
      <dc:creator>Karim Masmoudi</dc:creator>
      <pubDate>Sat, 10 Oct 2026 13:57:28 +0000</pubDate>
      <link>https://dev.to/kernelhunter/why-my-mac-memory-app-doesnt-try-to-empty-your-ram-2nb0</link>
      <guid>https://dev.to/kernelhunter/why-my-mac-memory-app-doesnt-try-to-empty-your-ram-2nb0</guid>
      <description>&lt;p&gt;Most Mac cleaners show you a big number and offer to make it smaller. That number is usually cached memory, which macOS is putting to good use, and shrinking it makes your Mac slower, not faster.&lt;/p&gt;

&lt;p&gt;I wanted a tool that tells me what's actually going on, so I built &lt;a href="https://github.com/Kernel-Hunter/boost" rel="noopener noreferrer"&gt;Boost&lt;/a&gt;, a free and open source menu bar app for memory and disk space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5too5vale4z8ftptdqxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5too5vale4z8ftptdqxl.png" alt="Boost's menu bar popover" width="680" height="1062"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "free memory" can mean on macOS
&lt;/h2&gt;

&lt;p&gt;On Windows, tools like Mem Reduct call &lt;code&gt;EmptyWorkingSet&lt;/code&gt;, which lets one process push another process's pages out to the pagefile. macOS has no public equivalent, as &lt;a href="https://github.com/apple-oss-distributions/xnu/blob/main/doc/vm/memorystatus_notify.md" rel="noopener noreferrer"&gt;Apple's kernel source&lt;/a&gt; shows. It wouldn't help much anyway: macOS compresses memory before it swaps, and reading a compressed page from RAM is much faster than reading it back from the SSD.&lt;/p&gt;

&lt;p&gt;So Boost uses what macOS does offer. Free Memory runs Apple's own &lt;code&gt;memory_pressure&lt;/code&gt; tool, which nudges the kernel to release idle pages and compress what it can. Boost takes readings before and after and reports only what actually stopped being used. It doesn't close apps, delete files or ask for a password, and it stops early if swap starts growing.&lt;/p&gt;

&lt;p&gt;On a typical run that's a few hundred MB to around a gigabyte. If your Mac isn't short on memory, there's little sitting idle to give back, and the header says your Mac is coping instead of inventing work. Settings has an aggressive mode that asks harder. It's off by default, because pushed far enough the kernel may kill something on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pausing instead of quitting
&lt;/h2&gt;

&lt;p&gt;The feature I use most is Pause. Boost freezes an app and its helper processes with &lt;code&gt;SIGSTOP&lt;/code&gt;, so it uses no CPU and keeps its state, and resumes it with &lt;code&gt;SIGCONT&lt;/code&gt;. You can pick apps to pause automatically after 5 to 60 minutes in the background, and Boost wakes each one the moment you switch to it.&lt;/p&gt;

&lt;p&gt;Two things keep this from going wrong. If Boost crashes or is killed, a small watchdog resumes everything it froze, so nothing stays stuck. And a hard-coded list of system processes (Dock, WindowServer, Finder, loginwindow and a few more) can never be paused, because freezing those freezes the desktop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2sbbxy09nzh3zih5pe1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2sbbxy09nzh3zih5pe1.png" alt="Boost's memory tab" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Disk cleanup that refuses the risky parts
&lt;/h2&gt;

&lt;p&gt;The Disk tab lists only caches that the tool that made them will rebuild: Homebrew, npm, pip, uv, Cargo, Gradle, Xcode DerivedData, simulator caches, app caches and logs. It won't touch Downloads, &lt;code&gt;node_modules&lt;/code&gt; found by name, or iOS backups. Nothing is ticked for you.&lt;/p&gt;

&lt;p&gt;Every removal goes through two checks: an allowlist of what may be scanned, and a second check of the resolved path right before deleting. A symlink inside a cache that points at your documents is how tools like this destroy data, and there's a test that builds exactly that trap.&lt;/p&gt;

&lt;p&gt;Projects (old &lt;code&gt;node_modules&lt;/code&gt;, Rust &lt;code&gt;target&lt;/code&gt;, SwiftPM &lt;code&gt;.build&lt;/code&gt;, virtualenvs) and Uninstall move things to the Trash, with Undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;History: a reading every minute, kept for a week, charted over 2 hours, 24 hours or 7 days, with the peak, the average and how many times the Mac went into swap.&lt;/li&gt;
&lt;li&gt;Alerts when swap passes 1, 2 or 4 GB, or when memory stays tight for a full minute, naming the app using the most. A spike that passes on its own is ignored.&lt;/li&gt;
&lt;li&gt;Process sampling is native (&lt;code&gt;sysctl&lt;/code&gt; and &lt;code&gt;libproc&lt;/code&gt;). Shelling out to &lt;code&gt;ps&lt;/code&gt; cost 111 ms per refresh and kept idle CPU near 20%.&lt;/li&gt;
&lt;li&gt;No analytics, no telemetry, no update check, no network access at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; kernel-hunter/boost/boost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or build it from source with Command Line Tools; Xcode isn't needed. macOS 14 or later, Apple silicon and Intel, GPL-3.0. It's signed with a local certificate rather than notarized, and the cask clears the quarantine flag.&lt;/p&gt;

&lt;p&gt;There's also a &lt;a href="https://github.com/Kernel-Hunter/boost/blob/main/docs/boost-ad.mp4" rel="noopener noreferrer"&gt;30-second overview video&lt;/a&gt; with sound.&lt;/p&gt;

&lt;p&gt;I'd like to hear what a memory tool should show you when there's nothing to fix.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Kernel-Hunter" rel="noopener noreferrer"&gt;
        Kernel-Hunter
      &lt;/a&gt; / &lt;a href="https://github.com/Kernel-Hunter/boost" rel="noopener noreferrer"&gt;
        boost
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A memory and disk utility for macOS that tells you the truth. Pause apps instead of closing them, reclaim disk space that is genuinely safe to reclaim.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Boost&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A free, open source Mac app for memory and disk space. It lives in your menu
bar, shows what is using your RAM, freezes apps you are not using, and finds
caches and leftovers that are safe to remove.&lt;/p&gt;
&lt;p&gt;If you have looked at CleanMyMac, iStat Menus or AppCleaner and did not want a
subscription, this covers the parts most people actually use.&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Kernel-Hunter/boost/docs/images/popover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FKernel-Hunter%2Fboost%2FHEAD%2Fdocs%2Fimages%2Fpopover.png" alt="Boost's menu bar popover: a memory pressure gauge, a graph, the apps using the most memory, and free disk space" width="340"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;brew install --cask kernel-hunter/boost/boost&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or build it yourself, which takes about as long and involves no Gatekeeper at
all:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/Kernel-Hunter/boost.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; boost &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./Scripts/build.sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;macOS 14 or later, Apple Silicon and Intel, one universal binary. It builds with
Command Line Tools, so Xcode is not required. If Boost is useful to you, a star
helps other people find it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On signing.&lt;/strong&gt; Boost is signed with a local certificate, not notarized
because notarizing needs a paid Apple Developer account. A downloaded &lt;code&gt;.zip&lt;/code&gt;
will be stopped…&lt;/p&gt;
&lt;/blockquote&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Kernel-Hunter/boost" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>macos</category>
      <category>swift</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I run Claude Code with permission prompts off, so I put it in a VM</title>
      <dc:creator>Karim Masmoudi</dc:creator>
      <pubDate>Sat, 10 Oct 2026 13:29:00 +0000</pubDate>
      <link>https://dev.to/kernelhunter/i-run-claude-code-with-permission-prompts-off-so-i-put-it-in-a-vm-2eam</link>
      <guid>https://dev.to/kernelhunter/i-run-claude-code-with-permission-prompts-off-so-i-put-it-in-a-vm-2eam</guid>
      <description>&lt;p&gt;I use Claude Code every day, and I run it with permission prompts off. It's much faster that way. You give it a task, go do something else, and come back to a finished change.&lt;/p&gt;

&lt;p&gt;The downside is finding out afterwards what else it did. Git can bring back tracked files, but not untracked or ignored ones, and not anything outside the repo.&lt;/p&gt;

&lt;p&gt;Most agent sandboxes I tried mount the project folder into a container or VM read-write. That stops the agent from touching the rest of my Mac, but its changes still land in my real files as it works. By the time I look, they're already there.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/Kernel-Hunter/mudroom" rel="noopener noreferrer"&gt;Mudroom&lt;/a&gt;, a free and open source Mac app and CLI that keeps the agent off the real folder completely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3tvieqybpgt3eb6ioxd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3tvieqybpgt3eb6ioxd.gif" alt="Mudroom demo" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Mudroom makes an APFS copy-on-write clone of the whole project folder, untracked and ignored files included. The clone takes no extra disk until files change.&lt;/li&gt;
&lt;li&gt;It boots a Linux micro-VM with Apple's &lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; tool and mounts only the clone. Your real folder is never mounted.&lt;/li&gt;
&lt;li&gt;Claude Code, Codex, Gemini CLI, opencode, Aider or any command runs inside, in a normal Terminal window, so you can still talk to the agent.&lt;/li&gt;
&lt;li&gt;When the agent exits, you get a diff that looks like a pull request: added, modified, deleted and permission changes, file by file. Modified files have a checkbox per hunk.&lt;/li&gt;
&lt;li&gt;You apply everything, some files, or single hunks. Every apply can be undone. If you edited a file yourself while the agent was running, it's flagged and never overwritten.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same thing from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mudroom run ~/code/myapp &lt;span class="nt"&gt;--&lt;/span&gt; claude &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
mudroom diff last
mudroom apply last &lt;span class="nt"&gt;--all&lt;/span&gt;
mudroom undo last
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The network part
&lt;/h2&gt;

&lt;p&gt;By default the VM sits on a host-only network with no DNS and no route out. The only way out is a logging proxy that lets through the agent's own API hosts (for Claude Code that's api.anthropic.com and a few sign-in hosts) plus anything you add.&lt;/p&gt;

&lt;p&gt;The review screen has a Network tab that lists every host the VM tried to reach, blocked ones first. If something legitimate got blocked, one click adds it to the project's allowlist for the next run. &lt;code&gt;mudroom network check&lt;/code&gt; tries to get around the proxy from inside the VM and prints what it could and couldn't reach.&lt;/p&gt;
&lt;h2&gt;
  
  
  Signing in once
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mudroom agent login claude&lt;/code&gt; (or "Use my Claude account" in the app) runs &lt;code&gt;claude setup-token&lt;/code&gt; and stores the token in the macOS Keychain, so later sessions start signed in. Your real &lt;code&gt;~/.claude&lt;/code&gt; is never mounted. &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; works too.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it doesn't do yet
&lt;/h2&gt;

&lt;p&gt;I'd rather list these up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app and the VM need macOS 26 or later on Apple silicon. Linux and Intel Macs get a CLI-only build on Docker or Podman, which is a container, not a VM.&lt;/li&gt;
&lt;li&gt;The VM can reach services on your Mac that listen on 0.0.0.0. The network check reports it. I don't have a fix yet.&lt;/li&gt;
&lt;li&gt;While a session runs, Apple's container runtime keeps the session environment, tokens included, in a config file that any process running as you can read.&lt;/li&gt;
&lt;li&gt;The allowlist works on host names. There's no TLS inspection.&lt;/li&gt;
&lt;li&gt;The app isn't notarized. The install script and Homebrew cask clear the quarantine flag.&lt;/li&gt;
&lt;li&gt;Lockfiles and generated files make noisy diffs. It's early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your project is a git repo and you're happy reviewing with git, Docker Sandboxes' clone mode gets you close. Mudroom also covers untracked and ignored files, folders that aren't repos, and gives you a review screen and an undo for each apply.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; kernel-hunter/tap/mudroom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;or the one-line install script in the &lt;a href="https://github.com/Kernel-Hunter/mudroom" rel="noopener noreferrer"&gt;README&lt;/a&gt;. There's also a &lt;a href="https://github.com/Kernel-Hunter/mudroom/blob/main/docs/mudroom-ad.mp4" rel="noopener noreferrer"&gt;33-second overview video&lt;/a&gt; with sound.&lt;/p&gt;

&lt;p&gt;It's MIT licensed and free. I'd like to hear from people who run agents unattended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the end of the run the right time to review, or would you want checkpoints in the middle?&lt;/li&gt;
&lt;li&gt;How should generated files and lockfiles show up in the diff?&lt;/li&gt;
&lt;li&gt;Which agents or setups should I test next?&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Kernel-Hunter" rel="noopener noreferrer"&gt;
        Kernel-Hunter
      &lt;/a&gt; / &lt;a href="https://github.com/Kernel-Hunter/mudroom" rel="noopener noreferrer"&gt;
        mudroom
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Run coding agents in a Linux VM on a clone of your project, then review and apply their changes like a pull request
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Mudroom&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Run coding agents in a sandbox, then review their changes like a pull request
before anything touches your project.&lt;/p&gt;
&lt;p&gt;Coding agents work best with permission prompts turned off, and that means
they can delete files or reach any host while you aren't looking. Mudroom
runs Claude Code, Codex, Gemini CLI, opencode, Aider (or any command) in a
Linux micro-VM, on a copy-on-write clone of your project, with network access
limited to the hosts you allow. Your real folder is never mounted. When the
agent is done you read the diff, apply all of it, some files, some hunks or
none, and undo any apply later.&lt;/p&gt;
&lt;p&gt;macOS app and CLI on Apple-silicon Macs. CLI only (with Docker or Podman) on
Linux and Intel Macs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Status: early prototype. Expect rough edges and breaking changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Kernel-Hunter/mudroom/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FKernel-Hunter%2Fmudroom%2FHEAD%2Fdocs%2Fdemo.gif" alt="Mudroom: start a session, review the diff, check the network log, apply"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Kernel-Hunter/mudroom/docs/mudroom-ad.mp4" rel="noopener noreferrer"&gt;Watch the 33-second overview (with sound)&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install (Apple-silicon Mac, macOS 26 or later):&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -fsSL&lt;/pre&gt;…
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Kernel-Hunter/mudroom" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>claudecode</category>
      <category>macos</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built my portfolio as an operating system, with no framework</title>
      <dc:creator>Karim Masmoudi</dc:creator>
      <pubDate>Fri, 18 Sep 2026 18:09:56 +0000</pubDate>
      <link>https://dev.to/kernelhunter/i-built-my-portfolio-as-an-operating-system-with-no-framework-4e98</link>
      <guid>https://dev.to/kernelhunter/i-built-my-portfolio-as-an-operating-system-with-no-framework-4e98</guid>
      <description>&lt;p&gt;A CV is a list of claims. An operating system is a thing you can use. That's the whole premise behind &lt;a href="https://karimmasmoudi.vercel.app/" rel="noopener noreferrer"&gt;KM/OS&lt;/a&gt;, my portfolio: instead of a page that &lt;em&gt;tells&lt;/em&gt; you I can build things, it's a desktop OS running in a browser tab: windows, a dock, a real virtual filesystem behind a Terminal app, a menu bar where every section of a normal portfolio is an application that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  No framework, on purpose
&lt;/h2&gt;

&lt;p&gt;Vanilla JavaScript in ES modules, bundled by &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; into a single file. Hand-written CSS over one set of design tokens. Two dependencies total, and both only at build time.&lt;/p&gt;

&lt;p&gt;For an interface built almost entirely from bespoke pieces (custom window chrome, custom drag/resize physics, a custom terminal emulator), a framework would have added a runtime, a build step, and a set of conventions in exchange for very little the project actually needed. A few decisions that came out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows are cloned from a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and driven by state classes, so all the open/close/minimize motion lives in CSS, not JS.&lt;/li&gt;
&lt;li&gt;Pointer Events handle every gesture (drag, resize, swipe), so behavior is identical across mouse, pen, and touch without separate code paths.&lt;/li&gt;
&lt;li&gt;One virtual filesystem is shared by the Terminal, a Finder-style file explorer, and a notes app. Edit a file in one, and it changes in the others.&lt;/li&gt;
&lt;li&gt;A service worker uses content-hashed bundle URLs (&lt;code&gt;app.js?b=&amp;lt;build-id&amp;gt;&lt;/code&gt;, regenerated every build). This came from a real bug: a stale cached bundle once ran against a freshly-deployed HTML document and silently broke the mobile UI, because the old JS was looking for DOM nodes the new shell no longer had. Giving the bundle's address a build id, not just its cache headers, means a stale cache has nothing to serve at a URL it's never seen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What leaves the browser
&lt;/h2&gt;

&lt;p&gt;Worth being explicit about, since it's a portfolio and not a toy: only two things call out to anything else. A question typed into the built-in AI assistant goes to the site's own server, then to a language-model provider. The API key never reaches the browser. The contact form posts to a forwarding service. Everything else is same-origin, enforced by a real &lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header (not a meta tag: meta tags can't express &lt;code&gt;frame-ancestors&lt;/code&gt;), which is also why there's no analytics script and no font CDN.&lt;/p&gt;

&lt;p&gt;Two inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags that do need to run are pinned by SHA-256 hash instead of &lt;code&gt;unsafe-inline&lt;/code&gt;, and there's a test that recomputes both hashes from the HTML source and fails the build loudly if either script's content ever changes without the hash being updated. That's a tripwire against the failure mode where an edited script just silently stops executing in production with zero build error.&lt;/p&gt;

&lt;p&gt;There's a public chat room too, which is the one place text from strangers gets rendered back to other visitors. Messages are length-capped, stripped of control characters, rate-limited server-side, and always written into the DOM as text content, never as markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't expect to spend time on: GEO
&lt;/h2&gt;

&lt;p&gt;Once the site worked, the next problem was that nobody could find it: a name search for "Karim Masmoudi" is dominated by an unrelated, actively-covered public figure with years of accumulated backlinks. That's not a code problem. But there &lt;em&gt;was&lt;/em&gt; a real code problem hiding next to it: the site was shipping &lt;code&gt;noindex, nofollow&lt;/code&gt; on every page, which meant none of the SEO work mattered until that came off first.&lt;/p&gt;

&lt;p&gt;After that, the more interesting part was optimizing for something that barely existed as a discipline a year ago: being &lt;em&gt;cited&lt;/em&gt; by AI answer engines (ChatGPT, Perplexity, Google AI Overviews), not just ranked by classic search. Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;FAQPage&lt;/code&gt; JSON-LD block, but built from real, already-written content rather than freehand copy: I split the AI assistant's own one-paragraph bio into sentences and mapped them to its own suggested questions, so the "answers" are literally the same facts the assistant already gives a human visitor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;/.well-known/ai.txt&lt;/code&gt;, &lt;code&gt;/ai/summary.json&lt;/code&gt;, &lt;code&gt;/ai/faq.json&lt;/code&gt;: the GEO-era equivalents of &lt;code&gt;robots.txt&lt;/code&gt;, generated at build time from the same data files, not maintained by hand.&lt;/li&gt;
&lt;li&gt;A JSON-LD &lt;code&gt;@graph&lt;/code&gt; where entities reference each other by &lt;code&gt;@id&lt;/code&gt; (Person → CollegeOrUniversity → WebSite) instead of nesting everything inline, since that's what lets a search engine resolve "who is this person" as a connected set of facts instead of a string that happens to appear on a page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dateModified&lt;/code&gt; sourced from &lt;code&gt;git log -1&lt;/code&gt;, not a hardcoded string that silently goes stale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one thing I deliberately &lt;em&gt;didn't&lt;/em&gt; add: a &lt;code&gt;SearchAction&lt;/code&gt; schema advertising site search to Google, because the site doesn't support URL-parameter search deep-linking. A rich-result annotation for a capability that doesn't work is worse than no annotation at all.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
