<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Windows on Luther Monson</title>
    <link>https://luth.io/categories/windows/</link>
    <description>Recent content in Windows on Luther Monson</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Sat, 03 Oct 2026 14:30:00 -0700</lastBuildDate>
    <atom:link href="https://luth.io/categories/windows/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Windows Grab-Bag: SIGTERM That Doesn&#39;t Exist and Sparse Files That Break Docker</title>
      <link>https://luth.io/blog/2026/10/windows-sigterm-sparse-files/</link>
      <pubDate>Sat, 03 Oct 2026 14:30:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/10/windows-sigterm-sparse-files/</guid>
      <description>&lt;p&gt;Two short ones from the Windows node of &lt;a href=&#34;https://luth.io/blog/2026/08/ephemerd-one-daemon-every-os/&#34;&gt;ephemerd&lt;/a&gt; from the last few weeks. Neither is&#xA;complicated once you know what&amp;rsquo;s going on. Both took longer than they should have to find, because the failure showed up a&#xA;long way from the cause.&lt;/p&gt;&#xA;&lt;h3 id=&#34;part-1-there-is-no-sigterm-on-windows&#34;&gt;Part 1: there is no SIGTERM on Windows&lt;/h3&gt;&#xA;&lt;p&gt;ephemerd can run a Cloudflare tunnel so GitHub webhooks reach a node behind NAT. It does that by running &lt;code&gt;cloudflared&lt;/code&gt; as a&#xA;managed subprocess. On Linux the child is tied to ephemerd&amp;rsquo;s lifetime by the kernel with &lt;code&gt;Pdeathsig&lt;/code&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>An Upstream PHP JIT Use-After-Free, and Why Our Default Flipped Twice</title>
      <link>https://luth.io/blog/2026/10/php-jit-use-after-free/</link>
      <pubDate>Thu, 01 Oct 2026 20:10:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/10/php-jit-use-after-free/</guid>
      <description>&lt;p&gt;In August, &lt;a href=&#34;https://github.com/ephpm/ephpm&#34;&gt;ePHPm&lt;/a&gt; turned PHP&amp;rsquo;s tracing JIT on by default in single-site serve mode.&#xA;Three days later it went back off on every platform, and it&amp;rsquo;s still off. The reason turned out to be a use-after-free&#xA;in PHP&amp;rsquo;s tracing JIT that has nothing to do with ePHPm. Stock &lt;code&gt;php -S&lt;/code&gt; with a stock Laravel skeleton dies on it in a&#xA;couple of requests. The fix is now released upstream. So this seemed like the right time to write up how we got there,&#xA;and what still has to happen before the default can flip again.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Cross-Platform Process Containment: Job Objects vs Process Groups vs cgroups</title>
      <link>https://luth.io/blog/2026/09/process-containment-job-objects-process-groups-cgroups/</link>
      <pubDate>Sat, 26 Sep 2026 16:20:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/09/process-containment-job-objects-process-groups-cgroups/</guid>
      <description>&lt;p&gt;Every turn a &lt;a href=&#34;https://luth.io/blog/2026/08/shipmates-persona-based-ai-coding-crews/&#34;&gt;shipmates&lt;/a&gt; crew member takes is a subprocess. It&amp;rsquo;s&#xA;&lt;code&gt;claude&lt;/code&gt; or the &lt;code&gt;codex&lt;/code&gt; app-server. That in turn spawns shells, &lt;code&gt;go test&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt; or whatever the task calls for. Two&#xA;things have to be true about that tree. When a turn ends or gets killed, the &lt;em&gt;whole&lt;/em&gt; tree goes, including the&#xA;grandchildren. And if somebody sets a memory or CPU budget, the budget applies to the whole tree, not just the&#xA;process I happened to start.&lt;/p&gt;</description>
    </item>
    <item>
      <title>A 1 TB Test: Building a Memory Watchdog for go test</title>
      <link>https://luth.io/blog/2026/09/memory-watchdog-for-go-test/</link>
      <pubDate>Tue, 22 Sep 2026 20:48:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/09/memory-watchdog-for-go-test/</guid>
      <description>&lt;p&gt;On August 16th a &lt;code&gt;server.test.exe&lt;/code&gt; from &lt;a href=&#34;https://luth.io/blog/2026/08/shipmates-persona-based-ai-coding-crews/&#34;&gt;shipmates&lt;/a&gt; climbed to&#xA;roughly &lt;strong&gt;1 TB of commit charge&lt;/strong&gt; on my Windows workstation. Windows Terminal was hosting several agent sessions in&#xA;tabs. It hard-faulted with &lt;code&gt;E_OUTOFMEMORY&lt;/code&gt;, and every one of those sessions died at the same moment. It wasn&amp;rsquo;t the&#xA;first time either. An earlier one hit about 236 GB through an infinite &lt;code&gt;filepath.Dir&lt;/code&gt; loop.&lt;/p&gt;&#xA;&lt;p&gt;The machine has 128 GB of RAM. Commit charge doesn&amp;rsquo;t care. It&amp;rsquo;s backed by the page file, and Windows will happily let&#xA;a process reserve-and-commit until the commit limit is gone. Then everything else on the desktop starts failing&#xA;allocations too.&lt;/p&gt;</description>
    </item>
    <item>
      <title>OPcache on Windows: Two Silent Failures</title>
      <link>https://luth.io/blog/2026/08/opcache-on-windows-two-silent-failures/</link>
      <pubDate>Sat, 29 Aug 2026 16:45:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/opcache-on-windows-two-silent-failures/</guid>
      <description>&lt;p&gt;Windows is the platform where &lt;a href=&#34;https://github.com/ephpm/ephpm&#34;&gt;ePHPm&lt;/a&gt; gets the least traffic and the most surprises.&#xA;Last week closed out two OPcache bugs that only exist there. Both were quiet in their own way. One killed the server&#xA;at startup with nothing in our own logs. The other didn&amp;rsquo;t kill anything. It just ran PHP 8.3 with no opcode cache at&#xA;all. For three weeks after a fixed SDK was published, every check stayed green.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Five Ways to Not Firewall a Windows Container (and the One That Works)</title>
      <link>https://luth.io/blog/2026/08/windows-container-egress-firewall/</link>
      <pubDate>Sat, 22 Aug 2026 19:40:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/windows-container-egress-firewall/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://luth.io/blog/2026/08/ephemerd-one-daemon-every-os/&#34;&gt;ephemerd&lt;/a&gt; runs CI jobs from pull requests. One of the rules is that a job can&#xA;reach the internet but not my LAN. On Linux that&amp;rsquo;s a few iptables rules in the &lt;code&gt;FORWARD&lt;/code&gt; path, and I didn&amp;rsquo;t think about it&#xA;much. On Windows I spent about a week finding out how many ways there are to write a firewall rule that Windows accepts,&#xA;lists and never applies.&lt;/p&gt;&#xA;&lt;p&gt;The setup is Windows Server 2025 (build 26100) with job containers running under Hyper-V isolation. They sit on the default&#xA;HNS NAT network that every Windows container tutorial uses. In ephemerd&amp;rsquo;s case that&amp;rsquo;s &lt;code&gt;10.88.0.0/16&lt;/code&gt; with gateway&#xA;&lt;code&gt;10.88.0.1&lt;/code&gt;. The target is the same as Linux. Block &lt;code&gt;10.0.0.0/8&lt;/code&gt;, &lt;code&gt;172.16.0.0/12&lt;/code&gt;, &lt;code&gt;192.168.0.0/16&lt;/code&gt; and &lt;code&gt;169.254.0.0/16&lt;/code&gt;,&#xA;and allow everything else.&lt;/p&gt;</description>
    </item>
    <item>
      <title>My Benchmark Was 4x Wrong Because of WSL</title>
      <link>https://luth.io/blog/2026/08/benchmark-4x-wrong-wsl-drvfs-cwd/</link>
      <pubDate>Thu, 20 Aug 2026 20:45:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/benchmark-4x-wrong-wsl-drvfs-cwd/</guid>
      <description>&lt;p&gt;I do most of my benchmarking on a Windows box in WSL2. It&amp;rsquo;s the machine on my desk with 32 threads and 62 GB of RAM.&#xA;Everybody who does that learns the first rule early: don&amp;rsquo;t put your data on &lt;code&gt;/mnt/c&lt;/code&gt;. DrvFs is the 9p-backed mount that&#xA;exposes the Windows drives inside WSL, and it&amp;rsquo;s slow for file-heavy work. So the databases, the docroot and the build&#xA;output all live on the WSL ext4 disk. My scaling harness for &lt;a href=&#34;https://github.com/ephpm/ephpm&#34;&gt;ePHPm&lt;/a&gt; even says so in&#xA;capital letters in its README.&lt;/p&gt;</description>
    </item>
    <item>
      <title>libxml2 2.14 Broke PHP Windows Builds - And Nobody Noticed</title>
      <link>https://luth.io/blog/2026/08/libxml2-2-14-broke-php-windows-builds/</link>
      <pubDate>Tue, 11 Aug 2026 20:36:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/libxml2-2-14-broke-php-windows-builds/</guid>
      <description>&lt;p&gt;Back in April I was getting the embed SAPI building on Windows for &lt;a href=&#34;https://github.com/ephpm/ephpm&#34;&gt;ePHPm&lt;/a&gt;. I was&#xA;using my fork of &lt;a href=&#34;https://github.com/crazywhalecc/static-php-cli&#34;&gt;static-php-cli&lt;/a&gt; (SPC). The link step for&#xA;&lt;code&gt;php8ts.dll&lt;/code&gt; fell over with a wall of errors that all looked like this:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;php8ts.dll.def : error LNK2001: unresolved external symbol UTF8ToHtml&#xA;php8ts.dll.def : error LNK2001: unresolved external symbol UTF8Toisolat1&#xA;php8ts.dll.def : error LNK2001: unresolved external symbol __htmlDefaultSAXHandler&#xA;...&#xA;php8ts.dll.def : error LNK2001: unresolved external symbol attributeDecl&#xA;...&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(That excerpt is from &lt;a href=&#34;https://github.com/php/php-src/issues/22752&#34;&gt;php/php-src#22752&lt;/a&gt;. It&amp;rsquo;s the same failure someone&#xA;else hit three months later. My log is long gone, and theirs is public.)&lt;/p&gt;</description>
    </item>
    <item>
      <title>Atomic File Replacement on NTFS Is Not rename &#43; fsync</title>
      <link>https://luth.io/blog/2026/08/ntfs-atomic-file-replacement-not-rename-fsync/</link>
      <pubDate>Thu, 06 Aug 2026 21:27:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/ntfs-atomic-file-replacement-not-rename-fsync/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/luthermonson/shipmates&#34;&gt;shipmates&lt;/a&gt; writes a lot of small state files that other processes read.&#xA;Voyage plans and state, tracker files. Each of them has to be replaced atomically, so a reader sees the old file or the&#xA;new one and never half of each. Each also has to be replaced durably. When the function returns success, a power cut&#xA;doesn&amp;rsquo;t take it back. Every Unix programmer knows the recipe for that. It doesn&amp;rsquo;t work on Windows, and the reasons are&#xA;more interesting than &amp;ldquo;Windows is different&amp;rdquo;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Rescuing a Windows Dynamic Disk Over USB with WSL2 and USB/IP</title>
      <link>https://luth.io/blog/2026/08/windows-dynamic-disk-usb-wsl2-usbip/</link>
      <pubDate>Sat, 01 Aug 2026 13:15:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/08/windows-dynamic-disk-usb-wsl2-usbip/</guid>
      <description>&lt;p&gt;I pulled a 1 TB NVMe out of an old Windows box, dropped it in a USB enclosure and plugged it in. Windows showed nothing.&#xA;No drive letter, no &amp;ldquo;initialize this disk&amp;rdquo; prompt, nothing in Explorer. Disk Management listed it as &lt;code&gt;Invalid&lt;/code&gt;. The&#xA;enclosure&amp;rsquo;s booklet said to &amp;ldquo;bring the disk online&amp;rdquo;, which silently did nothing. The data was completely intact. Windows&#xA;was refusing on &lt;strong&gt;policy&lt;/strong&gt; grounds, not because anything was broken. Getting it back meant going around the Windows&#xA;storage stack entirely. I handed the raw USB device to a Linux kernel that doesn&amp;rsquo;t share Windows&amp;rsquo; opinions.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Fixing Windows Archive Extraction in SPC</title>
      <link>https://luth.io/blog/2026/07/spc-windows-archive-extraction/</link>
      <pubDate>Sat, 18 Jul 2026 16:10:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/07/spc-windows-archive-extraction/</guid>
      <description>&lt;p&gt;One of the first things that broke when I started building PHP on Windows with&#xA;&lt;a href=&#34;https://github.com/crazywhalecc/static-php-cli&#34;&gt;static-php-cli&lt;/a&gt; (SPC) wasn&amp;rsquo;t PHP at all. It was unpacking the source&#xA;tarballs. libxml2, xz and a couple of others would die partway through extraction with:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ERROR: The pipe has been ended.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Over the last few months I&amp;rsquo;ve &amp;ldquo;fixed&amp;rdquo; this two different ways, and the maintainers fixed it a third. It&amp;rsquo;s a small piece&#xA;of code. But it&amp;rsquo;s a good example of how much Linux assumption hides in a single shell pipeline, so here&amp;rsquo;s the whole&#xA;thing.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Escaping WSL: Booting My Own Linux VM Through Hyper-V&#39;s HCS API</title>
      <link>https://luth.io/blog/2026/06/escaping-wsl-hcs-linux-vm/</link>
      <pubDate>Sun, 14 Jun 2026 20:12:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/06/escaping-wsl-hcs-linux-vm/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been building &lt;a href=&#34;https://github.com/ephpm/ephemerd&#34;&gt;ephemerd&lt;/a&gt;, a daemon that runs self-hosted GitHub Actions runners&#xA;with a fresh environment per job. On a Windows host, Windows jobs run in Hyper-V isolated containers. Linux jobs still&#xA;need a Linux kernel from somewhere. The obvious answer on Windows in 2026 is WSL2, and that&amp;rsquo;s what I used first. It worked&#xA;great from my terminal and fell over the moment ephemerd became a real Windows service.&lt;/p&gt;&#xA;&lt;h3 id=&#34;wsl2-doesnt-do-localsystem&#34;&gt;WSL2 doesn&amp;rsquo;t do LocalSystem&lt;/h3&gt;&#xA;&lt;p&gt;A Windows service that wants to create containers and Hyper-V VMs runs as &lt;code&gt;LocalSystem&lt;/code&gt;. WSL2 doesn&amp;rsquo;t support that context&#xA;at all. You get &lt;code&gt;WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED&lt;/code&gt; and that&amp;rsquo;s the end of the conversation. There&amp;rsquo;s no flag for it. WSL is a&#xA;per-user feature and a service account isn&amp;rsquo;t a user.&lt;/p&gt;</description>
    </item>
    <item>
      <title>go-diskfs: Getting Rock Ridge Working for Joliet on Windows</title>
      <link>https://luth.io/blog/2026/05/go-diskfs-rock-ridge-joliet-windows/</link>
      <pubDate>Sun, 17 May 2026 20:40:00 -0700</pubDate>
      <guid>https://luth.io/blog/2026/05/go-diskfs-rock-ridge-joliet-windows/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/luthermonson/go-proxmox&#34;&gt;go-proxmox&lt;/a&gt; has had a &lt;code&gt;CloudInit()&lt;/code&gt; helper since 2023. It builds a small&#xA;nocloud ISO in Go, uploads it to Proxmox and mounts it on the VM as a CD-ROM. It&amp;rsquo;s always worked fine for Linux guests. It&#xA;has never worked for Windows guests running &lt;a href=&#34;https://cloudbase.it/cloudbase-init/&#34;&gt;cloudbase-init&lt;/a&gt;. The reason is a&#xA;filesystem extension from 1995 called Joliet.&lt;/p&gt;&#xA;&lt;p&gt;Getting it fixed took three PRs to &lt;a href=&#34;https://github.com/diskfs/go-diskfs&#34;&gt;go-diskfs&lt;/a&gt; over about seven weeks. It also took a&#xA;bug report from a user who ran it on Windows. That turned out to be the most important part.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Maintaining a Go Tool That Edits /etc/hosts on Every OS</title>
      <link>https://luth.io/blog/2024/02/goodhosts/</link>
      <pubDate>Sat, 03 Feb 2024 19:22:00 -0700</pubDate>
      <guid>https://luth.io/blog/2024/02/goodhosts/</guid>
      <description>&lt;p&gt;Editing the hosts file sounds like the most boring problem in software. Read a text file, add a line, write it back. Then you find out&#xA;your binary is being shelled out to from a Ruby Vagrant plugin on a Windows laptop with UAC prompts. Your library is being called in a&#xA;loop by &lt;a href=&#34;https://github.com/ddev/ddev&#34;&gt;ddev&lt;/a&gt;. And every hosts file you parse was hand-edited by somebody at some point. I&amp;rsquo;ve been&#xA;maintaining &lt;a href=&#34;https://github.com/goodhosts&#34;&gt;goodhosts&lt;/a&gt; for a few years now. I just spent a chunk of the fall and the holidays on it, so&#xA;here&amp;rsquo;s what that looks like.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
