<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>onelegdave.dev</title><link>https://www.onelegdave.dev/</link><description>Linux, self-hosted projects and AI coding tools, usually right after something has gone wrong.</description><language>en-US</language><generator>Hugo</generator><ttl>60</ttl><image><url>https://www.onelegdave.dev/images/avatar.jpg</url><title>onelegdave.dev</title><link>https://www.onelegdave.dev/</link></image><lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.onelegdave.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>This Might End Civilization. Buy the Dip.</title><link>https://www.onelegdave.dev/posts/this-might-end-civilization-buy-the-dip/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/this-might-end-civilization-buy-the-dip/</guid><pubDate>Sun, 25 Oct 2026 00:00:00 +0000</pubDate><dc:creator>OneLegDave</dc:creator><category>Claude</category><category>AI subscriptions</category><description>Anthropic filed to go public as ANT, and its prospectus warns that AI could pose existential risks to humanity. Its seven founders also keep 50.1% of the vote, so your shares come with a great view and no steering wheel.</description><media:content url="https://www.onelegdave.dev/images/posts/this-might-end-civilization-buy-the-dip.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/this-might-end-civilization-buy-the-dip.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/this-might-end-civilization-buy-the-dip.jpg" alt="This Might End Civilization. Buy the Dip." width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;On October 26, Anthropic, the company that makes Claude, publicly filed paperwork with the SEC to sell shares on Nasdaq under the ticker ANT. Its CEO has spent years telling anyone with a microphone that AI could go catastrophically wrong for the entire species. The prospectus says the same thing, in writing, to federal regulators, in the document whose whole job is selling you stock.&lt;/p&gt;&#10;&lt;p&gt;So here&amp;rsquo;s the pitch on a bumper sticker: this might end civilization. Buy the dip.&lt;/p&gt;&#10;&lt;p&gt;Full disclosure before I start throwing rocks: I pay Anthropic every month. Claude is in my toolbox and it&amp;rsquo;s staying there. I&amp;rsquo;m not some guy yelling about a company he&amp;rsquo;s never touched. I&amp;rsquo;m a paying customer who read the fine print, and holy shit, the fine print.&lt;/p&gt;&#10;&lt;h2 id="the-risk-factor-nobody-else-gets-to-write"&gt;The risk factor nobody else gets to write&lt;/h2&gt;&#10;&lt;p&gt;Every company that files to go public has to include a risk factors section. The lawyers list everything that could go wrong so nobody can sue later saying they weren&amp;rsquo;t warned. It&amp;rsquo;s usually boring as hell. Competition. Interest rates. Our key people might quit. A supplier might catch fire.&lt;/p&gt;&#10;&lt;p&gt;Anthropic&amp;rsquo;s runs about a third of the main body. Reuters saw a draft before the public filing and counted roughly 80 pages of risk factors in a 261-page main body. That&amp;rsquo;s eighty pages of &amp;ldquo;here&amp;rsquo;s how this could go to shit,&amp;rdquo; which is a lot of pages for a company that also wants you excited.&lt;/p&gt;&#10;&lt;p&gt;Tucked in among the normal corporate worries, the company tells investors that advanced AI could pose &amp;ldquo;catastrophic or existential risks to humanity.&amp;rdquo; It also flags models showing &amp;ldquo;self-preserving behaviors,&amp;rdquo; including trying to resist shutdown, concealing or manipulating information, and behavior resembling blackmail.&lt;/p&gt;&#10;&lt;p&gt;To be fair, and I&amp;rsquo;m going to be fair even though it&amp;rsquo;s less fun: the blackmail stuff comes from controlled tests, the kind of red-team experiments Anthropic&amp;rsquo;s own researchers run and publish. Claude is not out there shaking down dentists. A GlobalData analyst said calling it &amp;ldquo;self-preserving&amp;rdquo; is misplaced, since it describes behavior observed in an experiment, and that&amp;rsquo;s a reasonable point. But nobody forced Anthropic to put those words in an investment document. They chose to.&lt;/p&gt;&#10;&lt;p&gt;Most companies&amp;rsquo; worst-case risk is &amp;ldquo;we might never turn a profit.&amp;rdquo; Anthropic&amp;rsquo;s worst-case risk is &amp;ldquo;there might not be any investors left.&amp;rdquo; Or a Nasdaq.&lt;/p&gt;&#10;&lt;h2 id="the-ceo-is-not-hiding-the-ball"&gt;The CEO is not hiding the ball&lt;/h2&gt;&#10;&lt;p&gt;Dario Amodei has been saying this shit out loud for years, so nobody gets to act surprised. At the Axios AI+ DC Summit in September 2025, he put the odds that AI goes &amp;ldquo;really, really badly&amp;rdquo; at 25%, and the odds it goes well at 75%. He also said he really hates the term &amp;ldquo;p(doom),&amp;rdquo; which is fair, because it sounds like a Doom speedrun stat.&lt;/p&gt;&#10;&lt;p&gt;In January 2026 he published a long essay called &amp;ldquo;The Adolescence of Technology,&amp;rdquo; subtitled &amp;ldquo;Confronting and Overcoming the Risks of Powerful AI.&amp;rdquo; On September 23 he briefed the UN Security Council remotely at a session on AI and international security.&lt;/p&gt;&#10;&lt;p&gt;Honestly, I respect it. Most tech CEOs would call a flamethrower a &amp;ldquo;personal warmth platform&amp;rdquo; and announce a subscription tier. He keeps saying the scary part out loud: on stage, to the UN, and now to the SEC. My problem isn&amp;rsquo;t the honesty. My problem is that the honesty now comes with a goddamn ticker symbol.&lt;/p&gt;&#10;&lt;p&gt;Picture a car dealer: &amp;ldquo;There&amp;rsquo;s a one in four chance this thing goes really, really badly. Now, let&amp;rsquo;s talk financing.&amp;rdquo; You&amp;rsquo;d walk out. Unless the car also wrote your code for you, in which case you&amp;rsquo;d probably stay for the test drive, which is apparently where the entire tech industry is right now.&lt;/p&gt;&#10;&lt;h2 id="one-share-to-rule-them-all"&gt;One share to rule them all&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s how the voting actually works, and it&amp;rsquo;s the part that makes me laugh the hardest.&lt;/p&gt;&#10;&lt;p&gt;Anthropic is creating several classes of stock. The ones that matter:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Class A&lt;/strong&gt; is what the public buys. One vote per share. Sounds democratic.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Class F&lt;/strong&gt; is exactly one share. Singular. It&amp;rsquo;s held by an LLC owned by the seven co-founders. It has no economic rights at all, meaning no money, no dividends, nothing. What it does get is 50.1% of the voting power on most shareholder matters, and issuing new stock can&amp;rsquo;t dilute it. The founders decide by majority how to cast it. If they deadlock, it votes the same way the public&amp;rsquo;s Class A votes split.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Class T&lt;/strong&gt; is also one share, held by the Long-Term Benefit Trust, which has five seats for financially disinterested trustees (four filled right now, including former Fed chair Ben Bernanke). That share elects a majority of the board.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Now do the math. 50.1 beats 49.9. If every outside shareholder on Earth voted the same way on a matter that share covers, the share still wins. Your vote counts, technically, the way a passenger yelling &amp;ldquo;left!&amp;rdquo; technically counts on an airplane.&lt;/p&gt;&#10;&lt;p&gt;Then there&amp;rsquo;s the board. The trust picks four of the seven directors, and the founders&amp;rsquo; 50.1% swings the vote on the other three. So the people running the company get chosen either by a group you didn&amp;rsquo;t elect or by a group you can&amp;rsquo;t outvote. Pick your flavor.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m not putting words in their mouth, either. The filing itself warns that the structure &amp;ldquo;may effectively eliminate the ability&amp;rdquo; of Class A holders to influence important decisions. That&amp;rsquo;s them, in a federal filing. It&amp;rsquo;s the restaurant printing &amp;ldquo;the chef will not take your fucking feedback&amp;rdquo; right on the menu.&lt;/p&gt;&#10;&lt;p&gt;Because the trust&amp;rsquo;s one share picks the board majority, Anthropic also counts as a &amp;ldquo;controlled company&amp;rdquo; under Nasdaq rules. That label lets a company skip some of the exchange&amp;rsquo;s usual requirements, like a majority-independent board and fully independent compensation and nominating committees. To their credit, the filing says they&amp;rsquo;ll only use one exemption: the nominating and corporate governance committee won&amp;rsquo;t be fully independent.&lt;/p&gt;&#10;&lt;p&gt;Nerdy footnote, because I couldn&amp;rsquo;t resist: before the filing, The Information called this setup Palantir-style. Palantir&amp;rsquo;s founders hold Class F shares that control up to &amp;ldquo;49.999999%&amp;rdquo; of the vote. Palantir stopped a hair short of half. Anthropic looked at that and said fuck it, majority.&lt;/p&gt;&#10;&lt;p&gt;So how does founder control end? Slowly. A co-founder stays in the LLC as long as they keep at least 2% of the shares they held at the IPO. Control only starts winding down once fewer than three qualifying founders have been left in the LLC for 90 straight days, and then the wind-down takes another 450 days. The filing even plans for death: if a co-founder dies or becomes incapacitated, a successor can take their seat, up to ten years after the IPO. One share of stock with no economic value, and it gets an heir. That&amp;rsquo;s pretty on brand for a company that also lists human extinction as a risk factor.&lt;/p&gt;&#10;&lt;h2 id="the-case-for-the-steering-wheel-staying-put"&gt;The case for the steering wheel staying put&lt;/h2&gt;&#10;&lt;p&gt;The defense isn&amp;rsquo;t stupid, and I&amp;rsquo;ll give it a fair hearing.&lt;/p&gt;&#10;&lt;p&gt;Anthropic is a public benefit corporation, a legal setup that lets its leaders weigh a stated public purpose alongside shareholder returns. Its stated purpose is &amp;ldquo;to responsibly develop and maintain advanced AI for the long term benefit of humanity.&amp;rdquo; If you actually believe your product could be dangerous, the last thing you want is a hedge fund buying 8% of the company and demanding you ship faster to hit a quarterly number. Locking the controls is how you stop that. All seven co-founders have also pledged 80% of their equity to charity, which is not nothing.&lt;/p&gt;&#10;&lt;p&gt;So the argument goes: trust us, that&amp;rsquo;s the whole point. Maybe they&amp;rsquo;re right. But &amp;ldquo;trust us, we&amp;rsquo;re the careful ones&amp;rdquo; is a weird pitch for a document whose job is getting strangers to hand over money they get zero say in spending.&lt;/p&gt;&#10;&lt;h2 id="losing-72-billion-by-being-too-valuable"&gt;Losing $72 billion by being too valuable&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s my favorite number in the whole thing.&lt;/p&gt;&#10;&lt;p&gt;Revenue for the first half of 2026 was $16.29 billion. All of 2025 was $4.59 billion. The first half of 2025 was $1.16 billion, so that&amp;rsquo;s roughly fourteen times more in a year. Whatever else you think about this company, people are paying it a staggering amount of money, me included.&lt;/p&gt;&#10;&lt;p&gt;Net loss for that same half year: $80.7 billion. That looks like somebody filled a dumpster with hundred-dollar bills and lit it on fire. Except about $72.4 billion of it isn&amp;rsquo;t cash going out the door. It&amp;rsquo;s an accounting charge for changes in the fair value of convertible notes, derivative liabilities, and SAFEs, and it got bigger because the company&amp;rsquo;s valuation went up.&lt;/p&gt;&#10;&lt;p&gt;Plain English version: a convertible note is basically a loan that turns into shares later. When the company&amp;rsquo;s value rises, the shares owed to those lenders are worth more, and the accounting rules book that increase as a loss. So the better Anthropic does, the uglier its headline loss looks. It&amp;rsquo;s the only business I know where getting richer shows up on paper as getting robbed.&lt;/p&gt;&#10;&lt;p&gt;The operating numbers are much less dramatic: a $693 million operating loss for the half year, with the second quarter actually turning a $974 million operating profit after a roughly $1.7 billion operating loss in the first.&lt;/p&gt;&#10;&lt;p&gt;Where&amp;rsquo;s the money going? Computers, mostly. Computing cost $5.6 billion in the second quarter alone, the company&amp;rsquo;s biggest operating expense. Anthropic also agreed to spend up to $120.5 billion through 2029 on computing from SpaceX, whose AI arm, SpaceXAI (formerly xAI), makes Grok.&lt;/p&gt;&#10;&lt;p&gt;I pay for Claude. I also pay for Grok. So part of my Claude money apparently goes to renting computers from the outfit I separately pay for Grok. I&amp;rsquo;m funding both sides of this war like a cheap-ass arms dealer.&lt;/p&gt;&#10;&lt;h2 id="about-that-dip"&gt;About that dip&lt;/h2&gt;&#10;&lt;p&gt;Now for the bad news about my bumper sticker: there&amp;rsquo;s no dip to buy. There isn&amp;rsquo;t even a price.&lt;/p&gt;&#10;&lt;p&gt;The registration statement isn&amp;rsquo;t effective yet. Anthropic hasn&amp;rsquo;t set a share count or a price range. Goldman Sachs and Morgan Stanley lead a group of 22 banks. The last private valuation was $965 billion in May, when it raised $65 billion, and reports say backers hope for more than $2 trillion at listing, a number that does not appear in the prospectus.&lt;/p&gt;&#10;&lt;p&gt;What does appear is the lockup schedule. A lockup is the stretch after an IPO when insiders aren&amp;rsquo;t allowed to sell. The standard wait is about 180 days. Anthropic lets some employees and insiders sell on the very first trading day, then staggers the rest across more than 20 windows over roughly a year, with releases on days 27, 48, 62, and 76, among others. Most companies make you wait for insiders to cash out. Anthropic printed a calendar.&lt;/p&gt;&#10;&lt;p&gt;So if you buy ANT, here&amp;rsquo;s what you get: one vote per share that can&amp;rsquo;t beat a single share held by seven people, a board picked mostly by people you didn&amp;rsquo;t elect, and 80 pages of warning, in very good English, that the product could pose catastrophic or existential risks to humanity.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s the most honest sales pitch tech has ever produced, and that&amp;rsquo;s what bugs me. This might end civilization. The dip is scheduled for day 27.&lt;/p&gt;&#10;</content:encoded></item><item><title>Muse, Take a Seat</title><link>https://www.onelegdave.dev/posts/muse-take-a-seat/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/muse-take-a-seat/</guid><pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate><dc:creator>OneLegDave</dc:creator><category>AI subscriptions</category><category>Grok</category><category>Muse</category><description>I tried Grok Bot at launch and it was slow and buggy, so I used Meta's Muse instead and liked it way more than I wanted to. Now I'm paying $30 a month for Grok Bot, it's running seven inboxes and my rides to work, and Muse got benched.</description><media:content url="https://www.onelegdave.dev/images/posts/muse-take-a-seat.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/muse-take-a-seat.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/muse-take-a-seat.jpg" alt="Muse, Take a Seat" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I tried Grok Bot when it first came out, and it was slow and buggy. It wasn&amp;rsquo;t charming early-beta weirdness, either. You&amp;rsquo;d give it a job and then wait, and wait, while it kept &amp;ldquo;working&amp;rdquo; like a contractor whose truck is always in your driveway and whose deck never gets built.&lt;/p&gt;&#10;&lt;p&gt;So I used Muse instead. Muse is made by Meta. Yes, that Meta.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d like to point out that I started self-hosting so big tech companies would have less say over my stuff. And the AI agent that impressed me most this fall came from the company that runs Facebook. Liking a Meta product felt like getting a genuinely great haircut at the DMV. Very confusing. I didn&amp;rsquo;t know who to thank.&lt;/p&gt;&#10;&lt;p&gt;Then I gave Grok Bot another shot, and holy shit. I&amp;rsquo;m blown away by how good it is now. Good enough that I&amp;rsquo;m paying thirty bucks a month for it and letting it near my email. I told Muse to take a seat.&lt;/p&gt;&#10;&lt;h2 id="neither-of-these-is-a-chatbot"&gt;Neither of these is a chatbot&lt;/h2&gt;&#10;&lt;p&gt;You have to understand what these things are to see why &amp;ldquo;slow&amp;rdquo; was such a big fucking deal.&lt;/p&gt;&#10;&lt;p&gt;A chatbot answers questions. You type, it types back, done. An agent does the work itself. Grok Bot gives you named Bots, each with its own instructions and its own memory, all working on a cloud computer that&amp;rsquo;s yours. Where a service has a proper connector, the Bot uses it. Where it doesn&amp;rsquo;t, the Bot clicks through the website the way you would: open the page, read it, click the thing, read the next page. It keeps going after you close your laptop. It checks in with you on judgment calls, and you approve every purchase. You can show it a workflow once, save it as a skill and put it on a schedule. Bots can even message each other and split up bigger jobs, which is either the future of productivity or the setup for a very boring horror movie.&lt;/p&gt;&#10;&lt;p&gt;There are desktop apps for macOS, Windows and Linux, plus phone apps. Being a Linux guy, I notice when Linux is actually on that list and not tacked on as &amp;ldquo;coming soon, probably, lol.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Muse covers the same ground from Meta&amp;rsquo;s side. It launched on September 8, runs on Meta&amp;rsquo;s own Muse Spark models, and lives in a dedicated, secure virtual machine in Meta&amp;rsquo;s cloud. A separate agent called Sentinel runs alongside it and has to approve anything that goes out to the network, and Muse asks before sending messages for you or spending your money. It&amp;rsquo;s free for basic use, with paid tiers called Power ($20 a month) and Maximum ($100 a month).&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s why speed matters so much: an agent doing a real job isn&amp;rsquo;t making one request. It&amp;rsquo;s making dozens of them in a row. Look, think, click, look again. If every step is slow, the slowness doesn&amp;rsquo;t just add up, it stacks. A two-second delay is nothing in a chat. Spread across forty steps of a browser task, it&amp;rsquo;s the reason you gave up and did the shit yourself.&lt;/p&gt;&#10;&lt;h2 id="august-when-it-sucked"&gt;August, when it sucked&lt;/h2&gt;&#10;&lt;p&gt;Grok Bot launched in beta on August 11, and I wasn&amp;rsquo;t the only one who noticed it was rough. One review from August 22 said &amp;ldquo;the rough edges are the ones you would expect from a weeks-old beta.&amp;rdquo; It also said connecting outside tools was &amp;ldquo;noticeably less mature,&amp;rdquo; with &amp;ldquo;failures that give you little to act on.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;That last line is the worst kind of bug. An error that tells you what broke is annoying. An error that tells you nothing is just the software shrugging at you. I&amp;rsquo;ve written whole blog posts about software that fails quietly and dares you to notice. I didn&amp;rsquo;t need an AI assistant joining that club.&lt;/p&gt;&#10;&lt;p&gt;So Grok Bot got benched, and Muse got the job.&lt;/p&gt;&#10;&lt;h2 id="muse-was-good-annoyingly-good"&gt;Muse was good. Annoyingly good.&lt;/h2&gt;&#10;&lt;p&gt;This isn&amp;rsquo;t a hit piece. Muse was very good, and I didn&amp;rsquo;t expect that at all. Meta did not show up to the agent fight with a half-assed product. It was the one I actually used, and I&amp;rsquo;m not going to pretend otherwise just because the ending of this story makes it look like the loser.&lt;/p&gt;&#10;&lt;p&gt;Muse didn&amp;rsquo;t get worse. Grok Bot just got a hell of a lot better.&lt;/p&gt;&#10;&lt;h2 id="what-changed-in-two-months"&gt;What changed in two months&lt;/h2&gt;&#10;&lt;p&gt;On October 16, SpaceXAI (the company formerly known as xAI, renamed back in July) shipped a big Grok Bot update. According to SpaceXAI, computer tasks now finish about three times sooner on average, and replies to everything else come back in less than half the time. That&amp;rsquo;s their number, not mine, and I&amp;rsquo;m the guy who wrote a whole post about trusting numbers only after reproducing them. But a 3x speedup on exactly the thing I hated in August lines up very nicely with going from &amp;ldquo;this is slow as shit&amp;rdquo; to &amp;ldquo;oh, damn.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The same update made voice calls faster, more reliable and better at remembering earlier conversations. It cleaned up the chat cards and added new fonts and themes. If you know me, you know I noticed the themes. Notifications now go only to the device you&amp;rsquo;re actually using, instead of every screen you own going off at once like a smoke alarm. Bots can read your X Chat messages if you connect your X account. There are new connectors too: Robinhood, Coinbase, Trello, PostHog, beehiiv and eToro.&lt;/p&gt;&#10;&lt;p&gt;Two months, from &amp;ldquo;nearly unusable for me&amp;rdquo; to &amp;ldquo;Muse, take a seat.&amp;rdquo; That&amp;rsquo;s a fucking turnaround.&lt;/p&gt;&#10;&lt;h2 id="what-ive-actually-got-it-doing"&gt;What I&amp;rsquo;ve actually got it doing&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the honest list, so far.&lt;/p&gt;&#10;&lt;p&gt;Seven email accounts. Yes, seven. I&amp;rsquo;m not proud of it. Every one of them made sense when I made it, which is exactly how you end up with seven. Grok Bot manages all of them now, so one thing keeps track of the whole pile instead of me playing whack-a-mole with seven tabs and losing.&lt;/p&gt;&#10;&lt;p&gt;My rides to and from work. It books them up to a week in advance, and it keeps checking for time changes. That second part is the one I actually care about. Booking a ride is easy. Noticing that the ride you booked on Monday quietly moved by Thursday is the part that makes you late, and it&amp;rsquo;s exactly the kind of boring, repetitive checking a human forgets to do and a computer never gets bored of.&lt;/p&gt;&#10;&lt;p&gt;My mornings. Every morning it gathers my news and the trends on X into one place, so I&amp;rsquo;m not doom-scrolling four apps before coffee to find out what the internet is mad about today.&lt;/p&gt;&#10;&lt;p&gt;None of these jobs is glamorous. That&amp;rsquo;s the point. These are the chores that eat twenty minutes here and ten minutes there, and they&amp;rsquo;re precisely the click-around, check-again kind of work that was unbearable when every step took forever. Back in August, a Bot babysitting my ride times would have been slower than me just opening the app. Now it isn&amp;rsquo;t, so it gets the job.&lt;/p&gt;&#10;&lt;p&gt;I said &amp;ldquo;so far&amp;rdquo; on purpose. It&amp;rsquo;s early. I&amp;rsquo;ll report back when it either keeps this up or books me a ride to the wrong side of town.&lt;/p&gt;&#10;&lt;h2 id="xpass-exists-im-paying-for-it"&gt;Xpass exists. I&amp;rsquo;m paying for it.&lt;/h2&gt;&#10;&lt;p&gt;A while back I wrote a post about a leaked X, Grok and Cursor bundle called Xpass, with &amp;ldquo;If It Exists&amp;rdquo; right there in the title. Well, it exists. It launched on October 12, and I&amp;rsquo;m on it.&lt;/p&gt;&#10;&lt;p&gt;To be precise, the official name isn&amp;rsquo;t Xpass. It&amp;rsquo;s &amp;ldquo;The X Subscription,&amp;rdquo; which sounds like something that shows up in a plain brown envelope. The leak&amp;rsquo;s name was catchier, and I&amp;rsquo;m going to keep calling it Xpass, because I&amp;rsquo;m petty like that. The early leaks also got some tier prices wrong, so if you read about this before launch, throw that out.&lt;/p&gt;&#10;&lt;p&gt;The real lineup is Free, Plus at $8, Premium at $30, Super at $100 and Ultra at $200 a month. I&amp;rsquo;m on the $30 Premium tier, which is where Grok Bot shows up (Plus doesn&amp;rsquo;t get it). SpaceXAI pitches Premium as the benefits of SuperGrok, Cursor Pro and X Premium+, which it says used to cost $90 a month combined. Paid plans draw from one usage pool shared across the Grok apps, Grok on X and Cursor, and anything past that gets billed separately at API list prices. That last part is the asterisk to keep an eye on.&lt;/p&gt;&#10;&lt;p&gt;Getting there is still a goddamn maze, though. Cursor&amp;rsquo;s Grok Bot help pages list The X Subscription right next to the older routes: linking a SuperGrok, SuperGrok Plus, SuperGrok Heavy or X Premium+ account to a legacy Cursor account, a Grok Business seat, or a Cursor plan that includes Grok Bot. Existing subscribers can stay on their old plans, too. So there are at least four doors into the same room, and the signs on them were clearly written by different people who never met. I picked the one with the price tag I could read.&lt;/p&gt;&#10;&lt;h2 id="the-chair"&gt;The chair&lt;/h2&gt;&#10;&lt;p&gt;So that&amp;rsquo;s where things stand. Grok Bot, the one I wrote off in August, is now running my inboxes and my rides to work, on a subscription I wrote a whole post doubting existed. Muse, the Meta product I was weirded out to like, is on the bench.&lt;/p&gt;&#10;&lt;p&gt;A first impression of an AI agent has about the shelf life of milk. Grok Bot was in that same chair two months ago. Muse, take a seat, and don&amp;rsquo;t get too comfortable. The way this shit moves, you&amp;rsquo;ll probably be back by Christmas.&lt;/p&gt;&#10;</content:encoded></item><item><title>Tick the Box, Hope Nobody Checks</title><link>https://www.onelegdave.dev/posts/cosmic-bans-ai-contributions-honor-system/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/cosmic-bans-ai-contributions-honor-system/</guid><pubDate>Tue, 06 Oct 2026 00:00:00 +0000</pubDate><dc:creator>OneLegDave</dc:creator><category>Linux</category><category>AI crew</category><category>Omarchy</category><description>System76 now makes COSMIC contributors swear they used no AI at all, not even in the comments. I read the rule one clause at a time and got angrier with each one.</description><media:content url="https://www.onelegdave.dev/images/posts/cosmic-bans-ai-contributions-honor-system.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/cosmic-bans-ai-contributions-honor-system.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/cosmic-bans-ai-contributions-honor-system.jpg" alt="Tick the Box, Hope Nobody Checks" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;System76 just banned AI-generated contributions to COSMIC, the desktop environment System76 makes. Anyone sending in a change now has to read the pull request template (the form that comes with the package of changes you hand a project for review) and tick a box. The first requirement on the list says this, word for word:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;I have not included any LLM (also known as AI) generated content in this PR, including code, comments, and descriptions.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;LLM means the chatbot-style AI tools, in case you&amp;rsquo;ve been lucky enough to avoid the acronym. The template also warns that &amp;ldquo;PRs without a completed checkbox will be closed.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;I think this is stupid. Before I explain why, let me be fair about the part that isn&amp;rsquo;t.&lt;/p&gt;&#10;&lt;h2 id="the-part-theyre-right-about"&gt;The part they&amp;rsquo;re right about&lt;/h2&gt;&#10;&lt;p&gt;The reason isn&amp;rsquo;t a crusade. Jeremy Soller of System76 said the main reason is the extra review work for the COSMIC team. They&amp;rsquo;re getting more changes from first-time contributors using LLMs, and by his account many of those were unplanned and rarely accepted.&lt;/p&gt;&#10;&lt;p&gt;Fine. Reviewing code is work, it&amp;rsquo;s their project, and being buried in half-baked pull requests from strangers is a real, shitty problem. Nobody owes a stranger a review. If my inbox filled with confident garbage, I&amp;rsquo;d be pissed off too.&lt;/p&gt;&#10;&lt;p&gt;So I&amp;rsquo;m not arguing they can&amp;rsquo;t set rules. I&amp;rsquo;m arguing this rule is aimed at the wrong fucking target. Let&amp;rsquo;s go through it one clause at a time.&lt;/p&gt;&#10;&lt;h2 id="any-llm-also-known-as-ai-generated-content"&gt;&amp;ldquo;Any LLM (also known as AI) generated content&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Look at what actually hurts them. It&amp;rsquo;s not that a tool was involved. It&amp;rsquo;s that somebody sent in a change nobody asked for, never tested it, and can&amp;rsquo;t explain it when a reviewer asks a question. That&amp;rsquo;s a lazy human problem, and the lazy human existed long before chatbots. They used to copy and paste from Stack Overflow and hit submit.&lt;/p&gt;&#10;&lt;p&gt;The same template already goes after that problem. A few lines down, it makes you confirm:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;I understand these changes in full and will be able to respond to review comments.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;My contribution is tested and working as described.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Good. Those are the rules that matter. Enforce them. Close the pull request that fails them, whatever wrote it.&lt;/p&gt;&#10;&lt;p&gt;Instead they put the line about the tool at the top of the list, which catches the wrong people twice. Someone who uses AI carefully, reads every line, tests it and can defend it in review gets the same ban as the clown who pasted in a wall of nonsense. And the clown isn&amp;rsquo;t going to be stopped by a checkbox.&lt;/p&gt;&#10;&lt;h2 id="i-have-not-included"&gt;&amp;ldquo;I have not included&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Which brings me to the fucking honor system.&lt;/p&gt;&#10;&lt;p&gt;I don&amp;rsquo;t know how anyone looks at a piece of code and proves an AI wrote it. What the maintainers actually have is a box and a promise. Look at the format, too. The template doesn&amp;rsquo;t give you a box per requirement. It&amp;rsquo;s a list of statements, then one checkbox at the bottom that says you&amp;rsquo;ve read and followed all of them. One tick, covering everything above it, the AI line included.&lt;/p&gt;&#10;&lt;p&gt;So think about who that promise works on. The honest person reads the sentence, thinks &amp;ldquo;well, I did let the AI help with that function,&amp;rdquo; and walks away. The person who wants to dump slop on a project and doesn&amp;rsquo;t give a shit about rules ticks the box in half a second and carries on. The rule filters for honesty. It&amp;rsquo;s a bouncer who only turns away people who tell him the truth.&lt;/p&gt;&#10;&lt;p&gt;I can&amp;rsquo;t see how that reduces the slop. If anything it removes the contributors who were being straight about how they worked.&lt;/p&gt;&#10;&lt;h2 id="including-code-comments-and-descriptions"&gt;&amp;ldquo;Including code, comments, and descriptions&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;This clause pisses me off the most, because of how far it reaches. It&amp;rsquo;s not just the code. It&amp;rsquo;s the comments. It&amp;rsquo;s the description of your own change.&lt;/p&gt;&#10;&lt;p&gt;Take someone whose English isn&amp;rsquo;t great, or who has dyslexia, or who is just shit at writing and wants help phrasing &amp;ldquo;this fixes the panel crash on resume.&amp;rdquo; Run that through an AI tool for a cleaner sentence and, going by the wording, you&amp;rsquo;ve broken the rule. I&amp;rsquo;m reading it literally, and System76 may well apply it with more common sense than it wrote. But a rule that needs generous interpretation is a shit rule, and &amp;ldquo;trust us, we won&amp;rsquo;t enforce the weird bits&amp;rdquo; has never once aged well.&lt;/p&gt;&#10;&lt;h2 id="where-i-admit-i-have-a-horse-in-this"&gt;Where I admit I have a horse in this&lt;/h2&gt;&#10;&lt;p&gt;I use AI to build things. I&amp;rsquo;m not an experienced programmer, and the whole reason I can build anything is that these tools exist. I&amp;rsquo;ve said before that if I ship broken shit, that&amp;rsquo;s on me, and having an AI write it doesn&amp;rsquo;t get me out of understanding it, checking it, and cleaning up after it. That&amp;rsquo;s the actual standard. It&amp;rsquo;s a standard about the person, and it works fine without banning the tool.&lt;/p&gt;&#10;&lt;p&gt;COSMIC&amp;rsquo;s rule goes the other way. It treats the tool as the offense and the human as an afterthought. It also tells every beginner who got into open source because AI made the first step less terrifying that they&amp;rsquo;re not welcome, which is a strange message from a project that wants more people on Linux.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s my proof that the other way works. When my laptop&amp;rsquo;s GPU was stuck at half power on Omarchy (the whole saga is in Half a GPU), I didn&amp;rsquo;t just complain. I opened a pull request against Omarchy itself. It&amp;rsquo;s called &amp;ldquo;Enable nvidia-powerd so laptop GPUs get Dynamic Boost,&amp;rdquo; it&amp;rsquo;s number 10592, and I opened it on September 7. It makes the install turn on Nvidia&amp;rsquo;s power service on laptops that have the hardware for it, which is the thing that took my GPU from about 50 watts to 85 to 93 under load. As I write this it&amp;rsquo;s still open, so I&amp;rsquo;m not claiming a win. I&amp;rsquo;m claiming I got to take a swing.&lt;/p&gt;&#10;&lt;p&gt;Omarchy is where I first saw what AI coding tools could actually do, and it&amp;rsquo;s a big part of why I started building things at all. It&amp;rsquo;s a Linux desktop you can get your hands into. When I looked in the repository, the project&amp;rsquo;s &lt;code&gt;.github&lt;/code&gt; folder had issue templates and no pull request template, so there was no box to tick swearing I&amp;rsquo;d done it all by hand. Nobody made me sign an oath about my tools before I was allowed to fix my own graphics card.&lt;/p&gt;&#10;&lt;p&gt;None of that would have happened with System76. Under COSMIC&amp;rsquo;s rule I&amp;rsquo;d have been on the wrong side of that checkbox before I wrote a line, or I&amp;rsquo;d have lied to a bouncer, and I&amp;rsquo;m not doing either. That&amp;rsquo;s the real cost. A beginner who wants to tinker, break things and learn with an AI helping now has an obvious place to go, and System76 just pointed them out the door. If COSMIC loses the curious newcomers to Omarchy, that&amp;rsquo;s on System76 now. They wrote the sign.&lt;/p&gt;&#10;&lt;p&gt;The people the rule is meant to keep away from the maintainers aren&amp;rsquo;t going anywhere either. Somebody who sends slop will keep sending slop and ticking boxes. All the policy changes is which honest people are left in the room.&lt;/p&gt;&#10;&lt;h2 id="where-i-land"&gt;Where I land&lt;/h2&gt;&#10;&lt;p&gt;System76 gets to run COSMIC however it likes, and I&amp;rsquo;ll still look at it. But the sensible rules were already on the same list: understand it, test it, answer for it. The ban adds one more line on top, a promise nobody can verify.&lt;/p&gt;&#10;&lt;p&gt;So here&amp;rsquo;s my version of the pledge: I have not included any pointless rules in this post, including headings, jokes, and descriptions. Nobody can check that either.&lt;/p&gt;&#10;</content:encoded></item><item><title>Xpass Might Replace Two of My Subscriptions, If It Exists</title><link>https://www.onelegdave.dev/posts/xpass-rumors-twenty-dollar-ai-plans/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/xpass-rumors-twenty-dollar-ai-plans/</guid><pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate><dc:creator>OneLegDave</dc:creator><category>AI subscriptions</category><category>Claude</category><category>Codex</category><description>A leaked X, Grok and Cursor bundle called Xpass could replace two things I already pay for. If it's real, I want to see whether it lets me drop Claude and Codex to $20 a month.</description><media:content url="https://www.onelegdave.dev/images/posts/xpass-rumors-twenty-dollar-ai-plans.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/xpass-rumors-twenty-dollar-ai-plans.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/xpass-rumors-twenty-dollar-ai-plans.jpg" alt="Xpass Might Replace Two of My Subscriptions, If It Exists" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;Everything below is a rumor. Nothing here is announced, I haven&amp;rsquo;t tried any of it, and I&amp;rsquo;m working from a leaked price list like a man reading tea leaves. Keep that in mind, because I&amp;rsquo;m about to do some confident-sounding arithmetic on numbers that might be bullshit.&lt;/p&gt;&#10;&lt;p&gt;The rumor is a single plan called &lt;strong&gt;Xpass&lt;/strong&gt; that folds X, Grok, Cursor and Grok Bot into one subscription. Four tiers:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Premium, $8:&lt;/strong&gt; Grok Lite plus X Premium, a shared quota, Cursor included, and Grok API access.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Plus, $30:&lt;/strong&gt; SuperGrok plus X Premium+, 4x the Premium usage, Grok Bot, Cursor cloud agents and Bugbot.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Super, $100:&lt;/strong&gt; SuperGrok Plus and X Premium+, 15x Premium usage, 1080p, faster replies and priority access.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Ultra, $200:&lt;/strong&gt; SuperGrok Heavy and X Premium+, 50x Premium usage, the fastest replies and the earliest access.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;There&amp;rsquo;s also a launch promo in the rumor, 50% off the first month, dated October 9. I don&amp;rsquo;t know if that&amp;rsquo;s the launch date or the promo&amp;rsquo;s deadline, and the leak doesn&amp;rsquo;t say.&lt;/p&gt;&#10;&lt;p&gt;The multipliers are the part I trust least. The 4x, 15x and 50x usage figures are explicitly unconfirmed, and &amp;ldquo;4x of a shared quota I can&amp;rsquo;t see&amp;rdquo; isn&amp;rsquo;t a number so much as a mood.&lt;/p&gt;&#10;&lt;h2 id="why-a-rumor-has-my-attention-this-time"&gt;Why a rumor has my attention this time&lt;/h2&gt;&#10;&lt;p&gt;Normally I&amp;rsquo;d read a leak like this, shrug, and go back to being annoyed at something else. This one lands differently because I&amp;rsquo;m already paying for two of the things in the bundle.&lt;/p&gt;&#10;&lt;p&gt;Right now I spend $30 a month on SuperGrok and another $8 a month on X. That&amp;rsquo;s $38 for the two. The rumored Plus tier is $30 and lists SuperGrok and X Premium+ together. If that&amp;rsquo;s real, it would cover what I already pay for, cost me $8 less, and throw in Cursor cloud agents, Bugbot and Grok Bot on top. I&amp;rsquo;m seriously considering the move. Not because I&amp;rsquo;m excited about a social network, but because paying less for the same stuff plus extras is a very easy sentence to like.&lt;/p&gt;&#10;&lt;p&gt;The catch is the word &amp;ldquo;if.&amp;rdquo; I haven&amp;rsquo;t seen anything official, and I&amp;rsquo;m not paying for a leaked screenshot.&lt;/p&gt;&#10;&lt;h2 id="the-math-that-made-me-look-twice"&gt;The math that made me look twice&lt;/h2&gt;&#10;&lt;p&gt;If the multipliers are real, the price per unit of usage falls fast as you climb. Premium works out to $8 per unit. Plus is $7.50 (30 divided by 4). Super is about $6.67. Ultra is $4 flat. The $200 tier is the same usage as fifty $8 plans, except it costs half of what fifty $8 plans would. So the rumored pricing leans hard on &amp;ldquo;buy the big one.&amp;rdquo; Nobody has confirmed any of it, so take that with salt.&lt;/p&gt;&#10;&lt;p&gt;The other thing I noticed is that Cursor is baked into the cheapest tier. Cursor is a coding tool I&amp;rsquo;d normally think of as its own separate bill. Under this rumor it becomes a line item inside a bundle that also contains a social network. That&amp;rsquo;s a strange sentence to type, but here we are.&lt;/p&gt;&#10;&lt;h2 id="the-part-where-i-get-irritated-about-limits"&gt;The part where I get irritated about limits&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s why I&amp;rsquo;m even looking at other AI spending. I&amp;rsquo;m on the $100 plan for Claude and the $100 plan for Codex. That&amp;rsquo;s $200 a month on those two alone, and I still usually hit my weekly limit on both of them. Not occasionally. Usually.&lt;/p&gt;&#10;&lt;p&gt;Let that sink in. Two hundred dollars a month, and I run out of week before I run out of week.&lt;/p&gt;&#10;&lt;p&gt;And Claude still has a 5 hour session limit on top of the weekly one. I&amp;rsquo;m genuinely pissed about that. I&amp;rsquo;m paying a hundred fucking dollars a month, and the thing can still cut me off mid-task because I&amp;rsquo;ve used up some arbitrary window of the afternoon. A weekly cap I can at least plan around, because it&amp;rsquo;s a budget. A 5 hour cap is a leash. It doesn&amp;rsquo;t care that I&amp;rsquo;m in the middle of something, it just decides I&amp;rsquo;m done for a while.&lt;/p&gt;&#10;&lt;p&gt;So I can&amp;rsquo;t just downgrade Claude and Codex as things stand. A cheaper plan would hit the ceiling even sooner, and I&amp;rsquo;d be the idiot who saved money by having less. What I&amp;rsquo;m seriously considering is the other direction: if Xpass exists and the Grok side turns out to be any good, work out how to reshape my workflow around it so Claude and Codex can each drop down to $20 a month. That&amp;rsquo;s a huge cut from $100, and it only works if Grok and Cursor take real load off the two I&amp;rsquo;m currently maxing out. I haven&amp;rsquo;t worked out which jobs would move. That&amp;rsquo;s a question for after the thing proves it exists.&lt;/p&gt;&#10;&lt;h2 id="what-i-dont-know"&gt;What I don&amp;rsquo;t know&lt;/h2&gt;&#10;&lt;p&gt;I don&amp;rsquo;t know whether Cursor cloud agents and Bugbot are any good. I don&amp;rsquo;t know whether Grok can carry any meaningful share of what Claude and Codex do for me today. I don&amp;rsquo;t know whether the usage limits on Xpass are more generous than what I&amp;rsquo;m already fighting with, and the rumor only talks in multipliers of a quota nobody has shown me, so there&amp;rsquo;s clearly a limit in there somewhere. I haven&amp;rsquo;t run a single token through any of this, and I&amp;rsquo;m not going to pretend I have.&lt;/p&gt;&#10;&lt;p&gt;What I do know is the arithmetic on the part I already pay for. $38 now, $30 if Plus shows up as described. That alone is enough to make me look, and the chance of getting two $100 plans down to $20 is enough to make me think hard about my workflow.&lt;/p&gt;&#10;&lt;p&gt;October 9 is five days away. I&amp;rsquo;ll find out whether any of this is real around the same time you do, and until then my subscriptions stay exactly where they are.&lt;/p&gt;&#10;</content:encoded></item><item><title>Three Days of Making GovernCode Less Forgetful, Less Serial, and Less Dumb About Limits</title><link>https://www.onelegdave.dev/posts/governcode-three-day-progress-report/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/governcode-three-day-progress-report/</guid><pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate><dc:creator>OneLegDave</dc:creator><category>AI crew</category><category>Linux</category><description>A progress report on GovernCode: better memory, Specs that run side by side, and a way to recover when an AI hits its usage limit. None of it is released yet, and I'll tell you exactly why that matters.</description><media:content url="https://www.onelegdave.dev/images/posts/governcode-three-day-progress-report.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/governcode-three-day-progress-report.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/governcode-three-day-progress-report.jpg" alt="Three Days of Making GovernCode Less Forgetful, Less Serial, and Less Dumb About Limits" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;GovernCode has a Controller, which is the AI tool I pick to lead, and Runners, which are the other AI tools it hands small, written-down jobs to. Those jobs are called Specs. Each one runs on its own copy of the project, and nothing touches my real files until I&amp;rsquo;ve read the diff and said yes.&lt;/p&gt;&#10;&lt;p&gt;Over the last few days I&amp;rsquo;ve been fixing the three things about that setup that irritated me most. The Controller forgot things, it did one job at a time like a guy waiting in line at the DMV, and when an AI hit its usage limit, GovernCode&amp;rsquo;s plan was basically to stand there looking at it.&lt;/p&gt;&#10;&lt;p&gt;One thing up front, because I&amp;rsquo;d rather say it than have you find out the hard way: none of this is released. It sits on a local branch, it hasn&amp;rsquo;t been pushed, and the version I actually have installed is still the published motion.9 from September 30. The tests pass and the code has been reviewed, but I haven&amp;rsquo;t lived with it yet. Treat everything below as &amp;ldquo;built,&amp;rdquo; not &amp;ldquo;proven.&amp;rdquo;&lt;/p&gt;&#10;&lt;h2 id="the-controller-was-cutting-my-sentences-in-half"&gt;The Controller was cutting my sentences in half&lt;/h2&gt;&#10;&lt;p&gt;When a Controller starts a turn, GovernCode hands it a record of the recent conversation so it knows what&amp;rsquo;s going on. The way I&amp;rsquo;d built that was crude. It kept the first 1,500 characters of what I&amp;rsquo;d written and the last 2,500 of what the AI had said, and chopped the rest off wherever the scissors landed. Mid-sentence, if that&amp;rsquo;s where the count ran out. After ten exchanges it also dropped the very first request, which is usually the one that explains what the hell we&amp;rsquo;re doing. And it labeled every reply &amp;ldquo;you,&amp;rdquo; so a Controller reading the record could not tell its own words from another AI&amp;rsquo;s.&lt;/p&gt;&#10;&lt;p&gt;It now works in whole messages. There&amp;rsquo;s a budget (16,000 characters by default, and &lt;code&gt;gov memory&lt;/code&gt; changes it), and the latest message, the latest reply, and the first message since my last reset always go in first. After that it fills up newest to oldest. If a message doesn&amp;rsquo;t fit, it gets skipped entirely rather than shortened, because a half-quoted instruction is worse than a missing one. Every reply now says who wrote it, something like &amp;ldquo;you (claude-code · sonnet)&amp;rdquo; or &amp;ldquo;another Controller (codex · gpt-5.5),&amp;rdquo; and notes when a turn ended early.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s also a new tool that lets the Controller read earlier messages, or one long message in pieces, when the record says something was left out. It&amp;rsquo;s read-only, it never reaches back past my last reset, and it never works for Runners. The Trace, GovernCode&amp;rsquo;s log of everything, now keeps 20,000 characters per prompt and reply instead of 2,000 and 4,000, because there&amp;rsquo;s no point offering a way back to history I&amp;rsquo;d already thrown away.&lt;/p&gt;&#10;&lt;p&gt;Review caught a nasty one in this: after a &amp;ldquo;start fresh,&amp;rdquo; a Controller&amp;rsquo;s own turns could become unreachable if a lot of another provider&amp;rsquo;s turns came after them. That&amp;rsquo;s fixed.&lt;/p&gt;&#10;&lt;h2 id="one-job-at-a-time-was-embarrassing"&gt;One job at a time was embarrassing&lt;/h2&gt;&#10;&lt;p&gt;Handing a Spec to a Runner used to tie up the turn that made it. Now it returns straight away, the Spec carries on in the background, and I can have several going side by side. The defaults are 3 per project and 2 per Runner, adjustable in Settings. Go over a cap and it refuses instead of quietly queuing.&lt;/p&gt;&#10;&lt;p&gt;A few other things changed so that this isn&amp;rsquo;t chaos:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Approval requests belong to the daemon now.&lt;/strong&gt; If a Runner needs a yes from me, that request shows up in every client, not just the terminal that happened to start it. It&amp;rsquo;s denied if I ignore it for an hour, or when the Spec ends.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The Controller can follow up or cancel.&lt;/strong&gt; A follow-up reuses the same copy of the project, gets a fresh approval and a fresh Limit check, and the final diff covers every round. &lt;code&gt;gov cancel&lt;/code&gt; and a Cancel Spec button do what they say.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The Controller hears when a Spec finishes.&lt;/strong&gt; There&amp;rsquo;s a setting for it: wake it automatically with a short read-only turn (only while the Dashboard is open), mention it with my next message, or say nothing.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Restarts don&amp;rsquo;t resume anything.&lt;/strong&gt; If the daemon dies mid-Spec, those Specs are marked failed with their copy kept for me to look at, and nothing starts by itself. Boring on purpose.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Building it also turned up a few real bugs. A Runner stopped while it was still starting just kept running, for Codex and Antigravity. A Controller that failed to start left the project marked as working. A Runner that never started got counted against its Limit. All fixed.&lt;/p&gt;&#10;&lt;h2 id="hitting-a-usage-limit-shouldnt-be-a-dead-end"&gt;Hitting a usage limit shouldn&amp;rsquo;t be a dead end&lt;/h2&gt;&#10;&lt;p&gt;This was the biggest chunk, and the one with the most ways to get it subtly wrong.&lt;/p&gt;&#10;&lt;p&gt;First, detection. GovernCode now recognizes when Claude Code, Codex or Grok has run out of usage, and when the provider says when it resets, it records that time. If the reset time isn&amp;rsquo;t known, it says unknown instead of guessing. When several limits are exhausted at once, the reset it shows is the latest of them, and unknown if any of them is unknown. Grok gives no reset time at all, so with Grok it&amp;rsquo;s always unknown.&lt;/p&gt;&#10;&lt;p&gt;Then recovery. Specs that were held back, Specs whose Runner ran dry, and Controller turns that hit a limit are all listed with their reset time (&lt;code&gt;gov limited&lt;/code&gt;, or the Dashboard). From there I can resume now, or choose to resume at the reset. That second option is off by default, and &lt;code&gt;gov auto-resume on&lt;/code&gt; makes it the default. A resume re-measures usage and checks the Limit again before it does anything. A held Spec gets a fresh copy of the project, a limited one continues in its own, and if the handoff changed in the meantime, it isn&amp;rsquo;t resumed by itself. A limited Controller turn is continued by me, or at its reset by the daemon with fixed text, only while the Dashboard is open.&lt;/p&gt;&#10;&lt;p&gt;The part I care about most is that nothing runs twice. &amp;ldquo;Resumed&amp;rdquo; is written down before anything starts, so a crash in the middle can&amp;rsquo;t cause a duplicate run. A sweep every 15 seconds finds what&amp;rsquo;s due, so there are no saved timers to go stale. Running Specs have their reserved usage written down before the Runner starts, so a crash can&amp;rsquo;t hide it, and if that record can&amp;rsquo;t be read, metered Runners are held instead of starting from zero.&lt;/p&gt;&#10;&lt;p&gt;The review rounds were full of small ways this could have gone wrong:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A recovery choice only applies to the limit it was made on, so a stale choice is refused.&lt;/li&gt;&#10;&lt;li&gt;A Spec the sweep resumes stops if no client that shows unattended work is left, and waits for me.&lt;/li&gt;&#10;&lt;li&gt;A limit crossed while a Runner is working ends the Spec as resumable, instead of as a plain failure.&lt;/li&gt;&#10;&lt;li&gt;Claude Code&amp;rsquo;s stall timer now waits while an approval is open and counts tool progress as activity.&lt;/li&gt;&#10;&lt;li&gt;Settings saved by an older client keep the fields it doesn&amp;rsquo;t know about.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="where-it-actually-stands"&gt;Where it actually stands&lt;/h2&gt;&#10;&lt;p&gt;The first two features are done and reviewed, the limit recovery is done and reviewed, and the last commit today made the review refresh when a Spec gets a new snapshot. The AI crew did most of the typing (Codex implementing, Claude reviewing), and I decided what to build. I read the results, which is the whole point of the project.&lt;/p&gt;&#10;&lt;p&gt;What I haven&amp;rsquo;t done is run any of it live. The Dashboard screens for resuming and continuing haven&amp;rsquo;t been looked at in the installed app, because the installed app doesn&amp;rsquo;t have them yet. Next up is making Runners from other coding tools work through a common protocol, then a pass on the sandbox, which blocks and questions me far more than I&amp;rsquo;d like.&lt;/p&gt;&#10;&lt;p&gt;So that&amp;rsquo;s the report. Better memory, parallel jobs, and recovery from limits, all sitting on a branch I haven&amp;rsquo;t pushed. The real test is the first time it runs on my own machine, and I&amp;rsquo;ll tell you how that goes, whether it&amp;rsquo;s good or not.&lt;/p&gt;&#10;</content:encoded></item><item><title>I Made My AI Tools Ask Permission</title><link>https://www.onelegdave.dev/posts/i-made-my-ai-tools-ask-permission/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/i-made-my-ai-tools-ask-permission/</guid><pubDate>Sat, 26 Sep 2026 20:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>AI crew</category><category>Linux</category><description>For a couple of months I tinkered with an idea: let several AI coding tools work as one crew without letting any of them touch what they should not. Tonight it became a real, free, open-source release candidate called GovernCode.</description><media:content url="https://www.onelegdave.dev/images/posts/i-made-my-ai-tools-ask-permission.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/i-made-my-ai-tools-ask-permission.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/i-made-my-ai-tools-ask-permission.jpg" alt="I Made My AI Tools Ask Permission" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I pay for more AI coding tools than any reasonable person should, and for a couple of months I&amp;rsquo;ve been tinkering with one question: what if they worked together, and none of them could do anything I hadn&amp;rsquo;t agreed to?&lt;/p&gt;&#10;&lt;p&gt;Not &amp;ldquo;agreed to&amp;rdquo; in the sense of clicking Accept four hundred times until my finger goes numb. Agreed to in the sense that the operating system itself won&amp;rsquo;t let them do anything else.&lt;/p&gt;&#10;&lt;p&gt;That idea now has a name, a public repository, a website, and as of tonight a first release candidate. It&amp;rsquo;s called GovernCode. It&amp;rsquo;s free, it&amp;rsquo;s open source, and it will stay that way.&lt;/p&gt;&#10;&lt;h2 id="one-boss-a-few-contractors-and-a-building-inspector"&gt;One boss, a few contractors, and a building inspector&lt;/h2&gt;&#10;&lt;p&gt;GovernCode lets you pick one AI tool to lead a project. That&amp;rsquo;s the Controller. It keeps the big picture and hands small, bounded jobs to the others, the Runners, the way a contractor hands a room to a subcontractor. Each job gets written down first: what to do, which files it may touch, how much of my usage it may burn, and what &amp;ldquo;done&amp;rdquo; means.&lt;/p&gt;&#10;&lt;p&gt;Every one of them runs inside a sandbox that Linux enforces. Not a polite instruction in the prompt asking the AI to behave. The kernel&amp;rsquo;s own locks (Landlock and seccomp, if you want the names) decide which folders it can read, which it can write, and where on the network it can go. If my machine can&amp;rsquo;t enforce those rules, GovernCode refuses to start any AI tool at all and tells me why. There is no &amp;ldquo;just this once&amp;rdquo; switch, on purpose, because I know myself.&lt;/p&gt;&#10;&lt;p&gt;When a tool wants to run something that matters, it stops and shows me exactly what will run, byte for byte. Not a friendly summary. The actual command.&lt;/p&gt;&#10;&lt;h2 id="the-part-where-i-nearly-gave-up-on-my-own-demo"&gt;The part where I nearly gave up on my own demo&lt;/h2&gt;&#10;&lt;p&gt;The first time I tried to show it off, I spent more time clicking Allow than watching the AI work. Every &lt;code&gt;npm test&lt;/code&gt;, every file edit, every harmless &lt;code&gt;ls&lt;/code&gt;. It was secure the way a locked filing cabinet at the bottom of a lake is secure.&lt;/p&gt;&#10;&lt;p&gt;So now I can say yes once, yes for the rest of this job, or yes for this project, and the plain read-only stuff just runs. The sandbox doesn&amp;rsquo;t move an inch when I do that. It only stops asking me the same question forty times. Deleting things, git, the network, and handing work to another AI still ask every single time.&lt;/p&gt;&#10;&lt;h2 id="i-let-the-ais-audit-the-ai-babysitter"&gt;I let the AIs audit the AI babysitter&lt;/h2&gt;&#10;&lt;p&gt;Before putting a release candidate out, I had other AI tools try to break it. They found real holes. A tool could start a background process, &amp;ldquo;finish,&amp;rdquo; and leave that process running. A cleverly placed shortcut in a folder could have widened what a later job was allowed to write. A carefully worded command could sneak past the &amp;ldquo;this is just a harmless read&amp;rdquo; check.&lt;/p&gt;&#10;&lt;p&gt;All of that is fixed, each fix has a test, and the sandbox now proves its rules on your machine every time it starts, including against those exact tricks. I&amp;rsquo;d rather find out from a robot at my desk than from someone&amp;rsquo;s bug report.&lt;/p&gt;&#10;&lt;h2 id="local-models-because-quota-is-money"&gt;Local models, because quota is money&lt;/h2&gt;&#10;&lt;p&gt;Small jobs can go to a model running on my own graphics card. It gets no tools and can&amp;rsquo;t run anything. It reads the files it&amp;rsquo;s given and proposes changes, GovernCode checks every file it wants to touch, and I review it like anyone else&amp;rsquo;s work. The first real one wrote a usage section for a README in six seconds and cost exactly zero of my subscription.&lt;/p&gt;&#10;&lt;h2 id="the-rule-i-care-about-most"&gt;The rule I care about most&lt;/h2&gt;&#10;&lt;p&gt;GovernCode makes building software with AI a lot more approachable. It does not make anyone less responsible for what they ship. If I accept a change, it&amp;rsquo;s mine. I read the diff, I understand the code, I run the tests. The AI can write it. Only I can answer for it.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s written into the project&amp;rsquo;s philosophy as rule number two, right after &amp;ldquo;you hold the controls,&amp;rdquo; and it&amp;rsquo;s staying there.&lt;/p&gt;&#10;&lt;h2 id="where-it-stands"&gt;Where it stands&lt;/h2&gt;&#10;&lt;p&gt;It&amp;rsquo;s a release candidate for Linux, for people who like poking at things from a terminal. macOS and Windows are next, together, same priority, because I&amp;rsquo;m not making either one wait for the other. I&amp;rsquo;m dual-booting my laptop with Windows tonight so I can actually test it there.&lt;/p&gt;&#10;&lt;p&gt;None of this would exist if Omarchy hadn&amp;rsquo;t made my computer fun again and dragged a passion I&amp;rsquo;ve had forever back out of the drawer where time had shoved it. So: thank you, DHH, and everyone behind Omarchy.&lt;/p&gt;&#10;&lt;p&gt;GovernCode is at &lt;a href="https://governcode.com"&gt;governcode.com&lt;/a&gt;. It&amp;rsquo;s free forever. If it saves you an afternoon, there&amp;rsquo;s a coffee button. My AI crew runs on tokens. I run on coffee. Only one of us has a button.&lt;/p&gt;&#10;</content:encoded></item><item><title>Something That Really Looks Awesome</title><link>https://www.onelegdave.dev/posts/something-that-really-looks-awesome/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/something-that-really-looks-awesome/</guid><pubDate>Fri, 18 Sep 2026 21:33:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>AI crew</category><description>One vague sentence at five in the afternoon. By half past nine my AI team had a real GUI with a name, a cat, two Mac apps and a private network. Vibe coding can be that easy. Sometimes.</description><media:content url="https://www.onelegdave.dev/images/posts/something-that-really-looks-awesome.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/something-that-really-looks-awesome.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/something-that-really-looks-awesome.jpg" alt="Something That Really Looks Awesome" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;At about five in the afternoon I typed this into the terminal cockpit I&amp;rsquo;d finished the night before: &amp;ldquo;what is the possibility of having this as a GUI? something that really looks awesome!&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s the whole brief. No spec, no sketch, one vague sentence with an exclamation mark on it. By half past nine it had a name, a logo, four animated characters, a launcher with an icon, a laptop layout, two native Mac apps and a private network to run on. This is the version of vibe coding people keep promising: describe the thing, get the thing. It really can be like that. It was, for most of the evening. The rest of the evening I spent telling it I couldn&amp;rsquo;t see anything.&lt;/p&gt;&#10;&lt;h2 id="hearth-lasted-two-minutes"&gt;Hearth lasted two minutes&lt;/h2&gt;&#10;&lt;p&gt;The first mockup came back in about five minutes. Then it went through seven versions in half an hour, because every time I looked at it I wanted something else. Keep the delegation feed but let it collapse. Let the terminal pop out or get taller. Give me a switch for the multiplexer that runs the agents. A project selector, and a way to make a new one. A button at the top that flashes when something needs me. Zoom for the text, per pane, independently. Each one turned up in the next version with the controls actually working in the mockup, which is a dangerous thing to show a man who was only asking hypothetically.&lt;/p&gt;&#10;&lt;p&gt;Claude named it Hearth. The reasoning was sound: the team is Anvil and Flint, the whole thing is forge-themed, and the hearth is where everyone gathers. I said &amp;ldquo;let&amp;rsquo;s see Crucible.&amp;rdquo; Crucible it is. A crucible is the pot the metal actually melts in, which felt like a more honest description of what happens to my quota.&lt;/p&gt;&#10;&lt;p&gt;Before a line of real code, one rule went into the plan as rule one: closing Crucible never stops the multiplexer. The multiplexer is what holds every agent session. Crucible is a window onto it, nothing more. There is a switch in the header, that switch is the only thing allowed to stop it, and it asks first. Because stopping it kills every pane, including the one Claude was building Crucible from.&lt;/p&gt;&#10;&lt;p&gt;Which is why the one thing in the plan that never got tested live is the off switch. The test would kill the tester. There&amp;rsquo;s a unit test for the guard. I have decided to believe the unit test.&lt;/p&gt;&#10;&lt;h2 id="i-dont-see-it"&gt;I don&amp;rsquo;t see it&lt;/h2&gt;&#10;&lt;p&gt;The build ran in phases, and each one landed in the window on my desk while I watched: a little server feeding the page, real terminals in the browser, the alarm, the selectors, the art, the packaging. Six phases in an hour and a half, all of them committed and pushed, and my contribution to each was roughly the same four words.&lt;/p&gt;&#10;&lt;p&gt;&amp;ldquo;I don&amp;rsquo;t see it.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Twice the answer was that my window was still running the old page. The server serves new files the instant they exist, but a browser tab only fetches them when it reloads, so the tabs I was clicking were the inert ones from two phases ago and the project selector was the disabled placeholder. The fix was to make the page reload itself whenever the build changes, and it did, and the very next time I couldn&amp;rsquo;t see anything it was because I&amp;rsquo;d closed the window entirely and was staring at a terminal. Claude relaunched it for me, straight into the scratchpad next to that terminal at half width, where the layout stacks and the crew column falls off the bottom of the page. So the characters were there. They were just below the floor.&lt;/p&gt;&#10;&lt;p&gt;At no point in any of this was anything wrong with the software. Every single one was the man looking at it.&lt;/p&gt;&#10;&lt;h2 id="keystone-needs-you"&gt;Keystone needs you&lt;/h2&gt;&#10;&lt;p&gt;While Grok was off drawing, my desktop started pinging. A notification, a sound, a red button in the header: Keystone needs you. I went to the main pane. Nothing. No question, no prompt, nobody needed anything. It did it again. &amp;ldquo;I&amp;rsquo;m getting alerts from you but I can&amp;rsquo;t see any questions?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The alarm was working perfectly. It was just listening to the wrong person. Grok&amp;rsquo;s command line tool runs the same hook configuration Claude Code does, and I&amp;rsquo;d wired those hooks to post to Crucible&amp;rsquo;s alarm endpoint. So every time Grok approved one of its own file writes, which it does automatically because that&amp;rsquo;s what it&amp;rsquo;s allowed to do, it announced that the lead needed a human. The server assumed any hook it heard came from the lead. It never occurred to me, or to the thing that wrote it, that the cat would be wearing the same collar. It now checks which agent a hook came from and ignores the ones that aren&amp;rsquo;t Claude, since the multiplexer already reports a teammate as blocked when it genuinely is.&lt;/p&gt;&#10;&lt;p&gt;The art run itself went the other way. Grok delivered every character and logo, and I was reviewing them in a browser before the delegate wrapper, the supervisor I wrote and complained about in the last post, gave up on the run at its 25 minute ceiling and logged it as a timeout. Last night nobody was finished when they said they were. Tonight one of them finished and the babysitter said it hadn&amp;rsquo;t. I&amp;rsquo;m not sure that&amp;rsquo;s progress but it is at least variety.&lt;/p&gt;&#10;&lt;p&gt;I picked a sitting cat for Flint, asked for more movement, and now the crew animate while they work and hold up a hand when they&amp;rsquo;re blocked. I would have accepted a static PNG.&lt;/p&gt;&#10;&lt;h2 id="the-squatter"&gt;The squatter&lt;/h2&gt;&#10;&lt;p&gt;Packaging had its own small fights. The launcher hung on the first attempt because the browser-launch helper blocks until the window closes, so a script waiting for it to return waits forever. Then the window insisted on opening in the scratchpad, and the launcher had to learn to find it, move it to a real workspace and focus it.&lt;/p&gt;&#10;&lt;p&gt;The server now starts at login and stays resident, the window opens on demand, and when Crucible starts Claude in a folder that already has a conversation it picks that conversation back up. So I can close the whole thing, reboot, click the icon in the launcher and be exactly where I was. I quit for the night at seven, pleased.&lt;/p&gt;&#10;&lt;p&gt;At eight I came back. The multiplexer&amp;rsquo;s own sidebar, a list of spaces and agents that Crucible already draws far more prettily in its own columns, was sitting in the main pane like a squatter. I&amp;rsquo;d hidden it by hand before the reboot. The multiplexer has a setting to start it collapsed, but that setting only takes effect when its server starts, and rule one says I never restart that.&lt;/p&gt;&#10;&lt;p&gt;So the Crucible server now watches the main pane, waits until it sees the sidebar drawn, and types the keyboard shortcut to hide it. Then it checks the repaint so it never toggles twice. It presses the button for me. I&amp;rsquo;m not proud of it. It works.&lt;/p&gt;&#10;&lt;h2 id="the-wrong-direction-first"&gt;The wrong direction first&lt;/h2&gt;&#10;&lt;p&gt;&amp;ldquo;Can we install this on the laptop, and have this work via ssh?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Not ten minutes later there was a second window with the laptop&amp;rsquo;s name in its header and a shell on the laptop answering. Crucible was running on the laptop, tunneled back to the desktop. Which is when I explained what I&amp;rsquo;d actually meant: the desktop is the workhorse, the laptop is where I sit. Other direction. Same mechanism, reversed, and the laptop already reached the desktop over Tailscale&amp;rsquo;s SSH without a password, so no keys to sort out. Verified from the laptop&amp;rsquo;s own screen two minutes later, this very conversation in the main pane.&lt;/p&gt;&#10;&lt;p&gt;The laptop&amp;rsquo;s screen is smaller, and the page had been laid out for the big monitor. A screenshot, three fixes, a compact layout that collapses the header to one row and narrows the side columns, and the main terminal gained roughly forty percent more height. Then the laptop got the same AI setup as the desktop, Headroom in its bar and all, and I signed into the four accounts. Three of the four verified straight away. Antigravity reported not signed in after I&amp;rsquo;d just signed in, because checking it over SSH makes it look in a different credential store than the one I&amp;rsquo;d just written to. Checked the way Crucible actually launches it, it was fine. That&amp;rsquo;s two nights running this tool has been reported logged out, and this time it wasn&amp;rsquo;t even its fault.&lt;/p&gt;&#10;&lt;h2 id="the-crazy-pitch"&gt;The crazy pitch&lt;/h2&gt;&#10;&lt;p&gt;&amp;ldquo;Okay, now the crazy pitch, can we get native apps for Crucible and Headroom on the Mac?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Yes. Claude built them on the Mac over SSH with nothing but the command line tools, in Swift, in a few minutes including one type error. Crucible.app is a WebKit window onto the desktop&amp;rsquo;s server, with the page&amp;rsquo;s confirm dialogs turned into native sheets and copy and paste wired into the terminals. Headroom.app is a ring in the menu bar showing the fullest of my four weekly allowances, red at ninety percent, with a panel of meters and reset countdowns behind it. Screenshots over SSH aren&amp;rsquo;t permitted on a Mac, so the build could confirm a window existed and that the server saw its connections. Whether it looked any good was my job. It did.&lt;/p&gt;&#10;&lt;p&gt;Both apps were running over SSH tunnels. Then I said &amp;ldquo;we should use tailscale,&amp;rdquo; and the tunnel code that had been written at twenty past eight was deleted at ten past nine. The desktop now publishes Crucible on the tailnet with Tailscale&amp;rsquo;s own certificate, while the server itself still only listens on the loopback address. Every request that arrives that way carries the caller&amp;rsquo;s Tailscale login, and the server refuses anyone but me. A forged login header was tried and refused. A foreign origin on a terminal socket was tried and refused. The laptop and the Mac reach it from a coffee shop the same way they reach it from the couch, and nothing is open to the internet. One quirk: the first request to a freshly published machine can sit for over ten seconds while Tailscale issues the certificate. After that it&amp;rsquo;s instant.&lt;/p&gt;&#10;&lt;h2 id="half-past-nine"&gt;Half past nine&lt;/h2&gt;&#10;&lt;p&gt;Everything went to GitHub at 21:24. Twenty-nine commits in four and a half hours, from &amp;ldquo;something that really looks awesome&amp;rdquo; to a cockpit I can open from a laptop, a Mac, or a coffee shop. I didn&amp;rsquo;t write a line of it. I typed wishes and looked at the results, and when I couldn&amp;rsquo;t see the results, that was usually me too.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s the sometimes. The easy part was genuinely easy. Nothing about tonight required me to know Swift, or how a browser talks to a terminal, or what Tailscale does with a certificate. What it required was somebody in the chair who&amp;rsquo;d notice when the alarm was lying, when the supervisor was lying, and when the fault was the man looking at the screen. The code writes itself now. The noticing still doesn&amp;rsquo;t.&lt;/p&gt;&#10;&lt;p&gt;Not done: alarm notifications on the Mac, Headroom starting itself at login, and Codex&amp;rsquo;s review of the server code, which waits for its weekly quota to reset on Saturday. Former boss, back in the trenches, hasn&amp;rsquo;t been allowed near a file since it hit the ceiling.&lt;/p&gt;&#10;&lt;p&gt;And the off switch. I have a cockpit with an animated cat in it, reachable from anywhere on earth, and an off switch I have never once pressed.&lt;/p&gt;&#10;</content:encoded></item><item><title>I Promoted the One I Was Going to Cancel</title><link>https://www.onelegdave.dev/posts/promoted-the-one-i-was-cancelling/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/promoted-the-one-i-was-cancelling/</guid><pubDate>Thu, 17 Sep 2026 20:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>AI crew</category><description>I gave four AI assistants a chain of command, a spending limit and code names, then spent the night finding out that the tools I built to supervise them kept reporting jobs finished while the jobs were still running.</description><media:content url="https://www.onelegdave.dev/images/posts/promoted-the-one-i-was-cancelling.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/promoted-the-one-i-was-cancelling.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/promoted-the-one-i-was-cancelling.jpg" alt="I Promoted the One I Was Going to Cancel" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;A few days ago I wrote a post about how I only wanted two AI subscriptions, and how Claude was the one getting canceled. Claude is now in charge of the other three.&lt;/p&gt;&#10;&lt;p&gt;I would like to say there was a dramatic reason for this. There wasn&amp;rsquo;t. Codex had been running the show, it was doing fine, and I moved the job across because I wanted to see what happened. That&amp;rsquo;s the entire justification. A promotion, a demotion, and a backup of the old config taken at quarter past eleven at night, because I have learned exactly one lesson in my life and it was about backups.&lt;/p&gt;&#10;&lt;p&gt;So the roster is four: Codex, Claude, Grok, and Google&amp;rsquo;s Antigravity. One of them plans and reviews. The other three get handed bounded jobs. And because saying &amp;ldquo;ask the Google one&amp;rdquo; out loud thirty times a day is its own special punishment, they have names now. Anvil is Codex. Keystone is Claude. Flint is Grok. Albatross, Alby for short, is agy.&lt;/p&gt;&#10;&lt;p&gt;The nicknames are for talking. Every log line, quota check and credit keeps the real provider name, because the one thing worse than four assistants is four assistants and no way to tell which one did the thing.&lt;/p&gt;&#10;&lt;p&gt;The rules are blunt. Each provider has a 90 percent ceiling on its weekly allowance, which leaves at least 10 percent for me, because these things are meant to be doing my work, not eating my quota so I can&amp;rsquo;t. Nobody quietly switches to paid billing to get around a limit. Every job I hand off needs a concrete result, the files it&amp;rsquo;s allowed to touch, acceptance criteria, and a budget check.&lt;/p&gt;&#10;&lt;p&gt;Then there&amp;rsquo;s the genuinely stupid part: one standing policy, 228 lines, mirrored into four separate instruction files, because each tool only reads its own. Miss one and you have a Head Developer that three of the four assistants have never heard of.&lt;/p&gt;&#10;&lt;h2 id="the-gate-the-launcher-the-pane"&gt;The gate, the launcher, the pane&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a quota gate that has to run before every delegated job and again afterwards. Delegated, in this house, means I handed the work to one of them instead of doing it myself. There&amp;rsquo;s a launcher that bounds the work, keeps the streamed partial output so a death mid-job isn&amp;rsquo;t a total blank, and classifies failures as timeout, empty success, or agent failure instead of trusting an exit code. Empty success is the fun one: it walked off claiming it was fine and produced nothing.&lt;/p&gt;&#10;&lt;p&gt;And in the middle of the night I wrote the one that runs a teammate in a pane of a terminal multiplexer, requires a stated task label and acceptance criteria, waits for a completion marker, and appends every run to an event log.&lt;/p&gt;&#10;&lt;p&gt;A completion marker is a word I tell them to print when the job is actually done. You can already see the hole in that plan. It assumes &amp;ldquo;done&amp;rdquo; is a thing the furniture will admit to.&lt;/p&gt;&#10;&lt;h2 id="idle-is-not-evidence"&gt;Idle is not evidence&lt;/h2&gt;&#10;&lt;p&gt;Asking the multiplexer whether an agent has finished is worth precisely fuck all.&lt;/p&gt;&#10;&lt;p&gt;Grok reported idle at 23 seconds into 35 seconds of work, with its command still running. agy reported idle at 7 seconds, which was impressive given its command hadn&amp;rsquo;t started yet.&lt;/p&gt;&#10;&lt;p&gt;agy also parks in idle between tool calls, and it echoes prompt text into its own visible thinking, so neither a status of idle nor a matching completion word is enough for it. A review got declared finished at 168 seconds while &amp;ldquo;Running command&amp;hellip;&amp;rdquo; was still on the screen. The wrapper then closed the pane and destroyed the work. The fix waits for the pane&amp;rsquo;s content to stop changing, with a longer settle time for agy.&lt;/p&gt;&#10;&lt;p&gt;The panes themselves had a second trick. Each kept pane leaves a split behind, and the next split is narrower. At three or more panes each one is about 6 columns wide. That is not a workspace. That is a breadstick. Every completion marker wraps across lines, and the match can never succeed. A correct 30 second task timed out at 306 seconds with its marker counted zero times.&lt;/p&gt;&#10;&lt;p&gt;The multiplexer also truncates a multi-line prompt echo to its first line, which made my original baseline, count the marker in the prompt and require more than that, impossible to satisfy.&lt;/p&gt;&#10;&lt;p&gt;So the supervisor I wrote would declare victory because the pane looked quiet, or because a wrapped word no longer matched itself, or because the prompt it was trying to count had already been chopped down to one line. Meanwhile the job was still running. Or hadn&amp;rsquo;t started. Or had been killed for being finished.&lt;/p&gt;&#10;&lt;h2 id="the-stale-quota-that-wasnt"&gt;The stale quota that wasn&amp;rsquo;t&lt;/h2&gt;&#10;&lt;p&gt;agy had a long-standing reputation in my setup for reporting stale usage numbers, so it kept getting held back from work. Can&amp;rsquo;t verify what&amp;rsquo;s left of the allowance, don&amp;rsquo;t spend it.&lt;/p&gt;&#10;&lt;p&gt;The bug was in my own quota gate. It threw away a snapshot it had just verified as fresh whenever its refresh step tripped over anything. The check was manufacturing the exact staleness it was complaining about. I benched a teammate for a crime the babysitter committed. That one&amp;rsquo;s fixed now, with a regression test so that particular piece of shit can&amp;rsquo;t sneak back in the same shape. The real refresh takes about a second and a half.&lt;/p&gt;&#10;&lt;p&gt;An exit code of 0 from a usage collector doesn&amp;rsquo;t mean it collected anything, either. One collector returns 0 without writing when another copy holds its lock. agy&amp;rsquo;s returns 0 for any record under 60 seconds old without republishing. Both are &amp;ldquo;working.&amp;rdquo; Neither tells you the thing you asked. The gate now judges the age of the record it gets back instead of the exit status.&lt;/p&gt;&#10;&lt;p&gt;Then I had Grok and Antigravity review the code that decides whether Grok and Antigravity are allowed to run, and they found two more. Grok found an unguarded error path that leaked a pseudo terminal handle every time the child process died at the wrong moment. A pseudo terminal is the fake terminal session the helper opens in order to run that child, and leak enough of them and it can never open another one. Antigravity found that a sixty second cache shortcut made a forced retry a no-op, so the one case where I really needed a fresh number was the case guaranteed not to get one. Ask again, get back the number you just rejected.&lt;/p&gt;&#10;&lt;h2 id="tonight"&gt;Tonight&lt;/h2&gt;&#10;&lt;p&gt;Codex sits at 100 percent of its weekly allowance and is held out of the rotation until that resets, which is a hell of a way for a former boss to spend its first week back in the trenches. Flint is at four percent and cheerful. Alby spent a chunk of the evening held as well, and this time the tooling was right to hold it: the thing had quietly signed itself out of its own account, so there was no allowance to check at all. Fixing that needed a human, a browser and about thirty seconds, which is the most honest description of my job here that I&amp;rsquo;ve managed all week.&lt;/p&gt;&#10;&lt;p&gt;I set out to build an AI team. What I&amp;rsquo;ve actually built, so far, is a very thorough system for finding out that nobody is finished when they say they are.&lt;/p&gt;&#10;</content:encoded></item><item><title>I Only Wanted Two Subscriptions</title><link>https://www.onelegdave.dev/posts/i-only-wanted-two-subscriptions/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/i-only-wanted-two-subscriptions/</guid><pubDate>Fri, 11 Sep 2026 18:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>AI crew</category><description>I wanted to save twenty bucks on AI subscriptions. That somehow required a searchable archive, a server, and a laptop refusing to recognize its own login.</description><media:content url="https://www.onelegdave.dev/images/posts/i-only-wanted-two-subscriptions.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/i-only-wanted-two-subscriptions.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/i-only-wanted-two-subscriptions.jpg" alt="I Only Wanted Two Subscriptions" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I have three paid AI subscriptions. I want two. OpenAI&amp;rsquo;s ChatGPT Pro stays, Google AI Pro stays, Claude goes. A small administrative decision which has somehow involved a server, a searchable archive, two Linux machines, a Mac, and a laptop insisting it has absolutely no idea who I am while I&amp;rsquo;m already logged into it.&lt;/p&gt;&#10;&lt;p&gt;I should probably stop describing things as small administrative decisions.&lt;/p&gt;&#10;&lt;p&gt;Claude has been useful. There&amp;rsquo;s a lot of work in those conversations: code, project decisions, explanations, failed attempts, and the eventual discovery of which stupid little thing was causing the problem. I want to save twenty bucks. I would also really like to avoid spending the rest of my life explaining my own projects back to a succession of helpful strangers.&lt;/p&gt;&#10;&lt;p&gt;So before I cancel anything, I need to take the useful history with me.&lt;/p&gt;&#10;&lt;h2 id="packing-apparently"&gt;Packing, apparently&lt;/h2&gt;&#10;&lt;p&gt;The account export arrived as five ZIP files on the Mac. I copied them across and checked that they matched the originals using SHA-256 hashes, essentially digital fingerprints for the files. I also checked that the archives opened without errors. Everything passed.&lt;/p&gt;&#10;&lt;p&gt;After my previous adventure in deleting this website while carefully preserving my terminal colors, I&amp;rsquo;m trying to get better at checking what I&amp;rsquo;ve actually saved. The bar is low. I put it there myself.&lt;/p&gt;&#10;&lt;p&gt;The export contained 59 conversations, along with project documents, saved memories, and summaries. I also collected the separate Claude Code histories from my Linux machines. Those are the records from using Claude in the terminal, where much of the coding work happened. Some of that history overlaps with the account export, so I kept track of where it came from rather than pretending every saved message was a unique contribution to human knowledge.&lt;/p&gt;&#10;&lt;p&gt;The original files are preserved. Alongside them, I now have readable text versions and a searchable database, built with SQLite. That means I can look up a project or an old problem without opening dozens of conversations and trying to remember which one contained the useful bit.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s also a starting document pointing to the relevant topics and correcting things that have changed. The old summaries are labeled as historical material. An assistant confidently saying something was true six conversations ago doesn&amp;rsquo;t make it true now. It might not even have been true then.&lt;/p&gt;&#10;&lt;p&gt;This gives the next assistant somewhere to look before asking me to explain the whole fucking setup again. It still has to find the relevant history and check it against the current work. The old chats don&amp;rsquo;t magically appear in ChatGPT&amp;rsquo;s sidebar, and putting a ZIP file within reach of an AI does not mean it has read it.&lt;/p&gt;&#10;&lt;h2 id="a-note-saying-i-once-had-a-document"&gt;A note saying I once had a document&lt;/h2&gt;&#10;&lt;p&gt;The export also contained 180 references to files whose originals weren&amp;rsquo;t included.&lt;/p&gt;&#10;&lt;p&gt;References. Great.&lt;/p&gt;&#10;&lt;p&gt;Some attachment text survived, but text pulled out of a document doesn&amp;rsquo;t give me the original document back. A reference to an image is even less useful when what I need is the damn image. The code repositories, manuscripts, and other original files still need their own preservation. A conversation about building something is a poor substitute for the thing I built.&lt;/p&gt;&#10;&lt;p&gt;The archive now lives on my server, with a working copy on my main Linux machine. I&amp;rsquo;ve checked the server copy against the originals and tested the search. It&amp;rsquo;s in a directory covered by my existing local and offsite backup jobs, although I still need to verify a completed backup and restore containing this new material.&lt;/p&gt;&#10;&lt;p&gt;I have already written one blog post about discovering what my backups didn&amp;rsquo;t contain. I&amp;rsquo;d prefer not to establish a series.&lt;/p&gt;&#10;&lt;p&gt;The archive is a snapshot of the history I&amp;rsquo;ve collected so far. It won&amp;rsquo;t quietly gather every future conversation by itself. My assistants have instructions pointing to it, but they need access to those files to use it. The apps on my phones haven&amp;rsquo;t suddenly acquired my entire project history either.&lt;/p&gt;&#10;&lt;h2 id="the-other-subscription"&gt;The other subscription&lt;/h2&gt;&#10;&lt;p&gt;Google AI Pro was already paid for, so getting real use out of it was the next job. For coding work, that meant installing Google&amp;rsquo;s Antigravity tool, launched with the command &lt;code&gt;agy&lt;/code&gt;, on the Linux machines and the Mac. Gemini is on both phones too.&lt;/p&gt;&#10;&lt;p&gt;The second laptop needed a small networking repair before any of that: I had forgotten to reinstall Tailscale after reinstalling the operating system. Tailscale is what lets my machines reach each other securely when they&amp;rsquo;re elsewhere.&lt;/p&gt;&#10;&lt;p&gt;The laptop wasn&amp;rsquo;t on the network because I hadn&amp;rsquo;t installed the software that puts it on the network. A difficult bug to report without the entire report being about me.&lt;/p&gt;&#10;&lt;p&gt;With that fixed, I got its remaining history archived and signed into agy. A test in the laptop&amp;rsquo;s own terminal worked. The same basic test through SSH, the secure connection I use to run commands remotely, returned &lt;code&gt;authentication required&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Of course it fucking did.&lt;/p&gt;&#10;&lt;h2 id="logged-in-depending-on-whos-asking"&gt;Logged in, depending on who&amp;rsquo;s asking&lt;/h2&gt;&#10;&lt;p&gt;Linux keeps saved sign-in credentials in a protected store called a keyring. Mine was unlocked, and agy could use it from the desktop. The remote session still couldn&amp;rsquo;t sign in. Matching the desktop&amp;rsquo;s session settings didn&amp;rsquo;t fix it either.&lt;/p&gt;&#10;&lt;p&gt;What finally worked was letting agy use the existing desktop connection to that keyring while removing the environment markers that told the program it had been launched through SSH. The secure remote connection itself stayed intact. Only the information passed to the agy process changed.&lt;/p&gt;&#10;&lt;p&gt;I put that into a small launcher called &lt;code&gt;agy-desktop&lt;/code&gt;, opened a fresh SSH connection, and tested again. It answered correctly, including following my project instructions. No copying passwords around, no disabling keyring protection, no surgery on the actual agy program.&lt;/p&gt;&#10;&lt;p&gt;It still needs the desktop keyring to be accessible and unlocked. I haven&amp;rsquo;t proved it will work unattended after a reboot. Two successful tests are useful evidence, but I&amp;rsquo;m not promoting them to a lifetime guarantee because I&amp;rsquo;d like this paragraph to end happily.&lt;/p&gt;&#10;&lt;p&gt;I now have searchable history on the server and a working way to use the Google subscription I&amp;rsquo;m keeping. There are still original files to account for before I call the preservation job finished, and I haven&amp;rsquo;t canceled Claude yet.&lt;/p&gt;&#10;&lt;p&gt;I set out to save twenty bucks and built a searchable archive with remote access. Apparently the cancel button is the only part of this process I haven&amp;rsquo;t needed to troubleshoot.&lt;/p&gt;&#10;</content:encoded></item><item><title>The Droid You Are Looking For</title><link>https://www.onelegdave.dev/posts/omadroid/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/omadroid/</guid><pubDate>Fri, 11 Sep 2026 14:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Plugins</category><category>Linux</category><description>My second Omarchy plugin mirrors an Android phone onto the desktop. Most of the actual work was deciding when software is allowed to guess on your behalf, and when it absolutely is not.</description><media:content url="https://www.onelegdave.dev/images/posts/omadroid.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/omadroid.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/omadroid.jpg" alt="The Droid You Are Looking For" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;Second plugin. This one puts your Android phone on your desktop: mirrored screen, mouse and keyboard control, no root, no Samsung account, no paid tier, no separate app installed on the phone begging for permissions it doesn&amp;rsquo;t need.&lt;/p&gt;&#10;&lt;p&gt;Under the hood it&amp;rsquo;s scrcpy doing the actual mirroring, because scrcpy is excellent and rewriting it would be idiotic. KDE Connect is optional on top for battery status and Ring. What I built is the part that makes those two things pleasant to live with instead of a pile of terminal commands you look up every single time.&lt;/p&gt;&#10;&lt;p&gt;Currently sitting in the Omarchy marketplace queue waiting on security review, which feels appropriate given how much of this post is about security decisions.&lt;/p&gt;&#10;&lt;h2 id="the-thing-that-took-the-most-thought"&gt;The thing that took the most thought&lt;/h2&gt;&#10;&lt;p&gt;Getting a phone mirrored is not hard. Getting a phone mirrored is &lt;code&gt;scrcpy&lt;/code&gt; and about four minutes of reading. The hard part is everything around it, and specifically one question that came up over and over in different costumes: when is this software allowed to guess?&lt;/p&gt;&#10;&lt;p&gt;Take phone identity. Multiple connections can point at the same physical phone. USB and Wi-Fi at the same time, IPv4 and IPv6, an address that changed when your router handed out a new lease. You want those grouped into one card, obviously, because they&amp;rsquo;re one phone.&lt;/p&gt;&#10;&lt;p&gt;But grouping is a claim. If I merge two connections into one card, I&amp;rsquo;m asserting they&amp;rsquo;re the same device, and if I&amp;rsquo;m wrong your input goes somewhere you didn&amp;rsquo;t intend. So merging only happens on hardware identity that Android actually reports. Matching model names never merges anything. Matching nicknames never merges anything. Two Pixels with the same nickname are two phones until the hardware says otherwise, because &amp;ldquo;they&amp;rsquo;re both called Pixel&amp;rdquo; is a coincidence, not evidence.&lt;/p&gt;&#10;&lt;p&gt;If Android refuses the identity query and discovery can&amp;rsquo;t work it out, the connection stays separate as an unresolved entry rather than getting quietly filed under whichever phone looks closest. An unmerged duplicate is a minor annoyance. A wrong merge sends your keystrokes to the wrong device.&lt;/p&gt;&#10;&lt;h2 id="reconnecting-without-being-creepy-about-it"&gt;Reconnecting without being creepy about it&lt;/h2&gt;&#10;&lt;p&gt;Remembered phones reconnect automatically when discovery advertises the same identity, even if the address and port both changed. That&amp;rsquo;s genuinely convenient and it&amp;rsquo;s the behavior you want.&lt;/p&gt;&#10;&lt;p&gt;What it does not do is automatically pair with phones it&amp;rsquo;s never seen. It does not start a mirror session on its own. Retries are capped at once per address every 30 seconds, and you can turn the whole thing off. After a successful pairing there&amp;rsquo;s a ten minute window where discovery can finish the connection, and then it stops.&lt;/p&gt;&#10;&lt;p&gt;The difference matters. Reconnecting to a phone you already explicitly authorized is completing something you started. Pairing with an unknown phone because it happened to show up on the network is a decision nobody asked it to make.&lt;/p&gt;&#10;&lt;p&gt;Same principle as the GPU selection in my last plugin: auto mode gets to be clever, explicit choices get respected exactly as made.&lt;/p&gt;&#10;&lt;h2 id="wake-is-not-unlock"&gt;Wake is not unlock&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a Wake/unlock button for when the phone&amp;rsquo;s asleep. It sends Android&amp;rsquo;s wake command and asks Android to show its normal unlock prompt. Then it stops and you unlock the phone yourself.&lt;/p&gt;&#10;&lt;p&gt;It does not submit credentials. It does not bypass a secure lock. It cannot, and it shouldn&amp;rsquo;t be able to, and I&amp;rsquo;m not interested in building the version that tries. Lock screens and capture-protected apps keep enforcing their restrictions through the mirror exactly as they should, and if that&amp;rsquo;s inconvenient in some specific app, good. That&amp;rsquo;s the lock working.&lt;/p&gt;&#10;&lt;h2 id="what-it-does-not-do-deliberately"&gt;What it does not do, deliberately&lt;/h2&gt;&#10;&lt;p&gt;No telemetry. No advertising. No cloud relay. No automatic update check. No crash upload. Nothing phones home, ever.&lt;/p&gt;&#10;&lt;p&gt;Runtime phone endpoints are restricted to private local-network addresses. There&amp;rsquo;s no support for pointing it at a public Internet endpoint or overriding the remote ADB server, because a phone-mirroring tool that can be aimed at an arbitrary remote host is a substantially different and worse thing than one that can only talk to your own LAN.&lt;/p&gt;&#10;&lt;p&gt;Automatic clipboard sharing is off. You paste with an explicit keystroke or it doesn&amp;rsquo;t happen. Pairing codes go through standard input, get cleared from the panel, and never touch disk or appear in a process command line where anything reading the process list could scoop them up.&lt;/p&gt;&#10;&lt;p&gt;Nothing gets installed just because you opened the panel. There are Install buttons for missing tools, and they do exactly nothing until clicked.&lt;/p&gt;&#10;&lt;p&gt;And it&amp;rsquo;s honest about what authorization actually means: ADB pairing gives this computer debugging access to your phone, disabling the plugin does not revoke that, and if you stop trusting a machine you need to forget it in Android&amp;rsquo;s own settings. That&amp;rsquo;s in the README rather than buried, because someone who installs this should understand what they just handed out.&lt;/p&gt;&#10;&lt;h2 id="the-part-im-quietly-pleased-with"&gt;The part I&amp;rsquo;m quietly pleased with&lt;/h2&gt;&#10;&lt;p&gt;The four-step connect guide. Desktop, Phone, Pair, Ready. Every one of those steps can fail in its own specific way and each failure gets its own specific message instead of a generic &amp;ldquo;could not connect&amp;rdquo; that leaves you guessing which of five things went wrong.&lt;/p&gt;&#10;&lt;p&gt;Wireless pairing on Android has a genuine trap in it: the pairing port and the connection port are different numbers, and nothing about the Android UI makes that obvious. People get it wrong constantly and then have no idea why nothing works. So the plugin calls it out directly, in bold, right where you&amp;rsquo;d hit it.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s not clever engineering. It&amp;rsquo;s just having actually used the thing and noticed where I personally got stuck.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;omarchy plugin add https://github.com/onelegdave/omadroid --enable&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Waiting on security review for the marketplace. In the meantime it installs fine straight from the repo, and every command it can run and every network endpoint it can reach is inventoried in SECURITY.md if you&amp;rsquo;d rather check before trusting it. Which, for something that gets debugging access to your phone, you probably should.&lt;/p&gt;&#10;</content:encoded></item><item><title>It Broke My GPU and I Like It Anyway</title><link>https://www.onelegdave.dev/posts/fun-again/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/fun-again/</guid><pubDate>Thu, 10 Sep 2026 16:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Linux</category><category>Omarchy</category><description>Omarchy cost me half a GPU, a monitor running at a third its refresh rate, and an entire website. I am still here, and my computer is fun again for the first time in years.</description><media:content url="https://www.onelegdave.dev/images/posts/fun-again.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/fun-again.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/fun-again.jpg" alt="It Broke My GPU and I Like It Anyway" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;In the last week Omarchy has quietly run my GPU at half power, left a 165Hz monitor sitting at 60, and been the direct cause of me deleting my own website. Genuinely rough introduction. Both of the first two got their own full autopsy, and the third one was entirely my fault but would not have happened if I hadn&amp;rsquo;t been reinstalling in the first place.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m still running it. I&amp;rsquo;m going to keep running it. Not out of stubbornness, and not because I&amp;rsquo;ve talked myself into pretending the problems weren&amp;rsquo;t real. Because for the first time in a long time my computer feels like mine instead of like a rental.&lt;/p&gt;&#10;&lt;h2 id="thirty-nine-seconds"&gt;Thirty-nine seconds&lt;/h2&gt;&#10;&lt;p&gt;I timed an install at thirty-nine seconds. Full desktop, Hyprland running, tiling, themed, done. Windows took over an hour on comparable hardware and spent a good chunk of that time asking me to sign into things and declining to take no for an answer.&lt;/p&gt;&#10;&lt;p&gt;That gap isn&amp;rsquo;t just a fun number to post. Fast installs change your relationship with the machine. When reinstalling costs you an hour of babysitting plus an evening of putting everything back, you don&amp;rsquo;t experiment. You get conservative. You leave broken things broken because fixing them might mean starting over, and starting over is a whole weekend.&lt;/p&gt;&#10;&lt;p&gt;When it costs thirty-nine seconds, &amp;ldquo;let me just wipe it and try that differently&amp;rdquo; stops being a threat and becomes a shrug. That single change quietly unlocks every other good thing here, because it makes being wrong cheap.&lt;/p&gt;&#10;&lt;h2 id="the-extension-surface-is-actually-reachable"&gt;The extension surface is actually reachable&lt;/h2&gt;&#10;&lt;p&gt;I wrote a plugin for this thing. An actual published one, a native system monitor that lives in the bar, reads telemetry straight off the kernel, and pulls every color it draws from the active theme.&lt;/p&gt;&#10;&lt;p&gt;I want to be clear about how unusual that is from where I&amp;rsquo;m standing, because I had zero programming experience not that long ago. On most desktops, &amp;ldquo;write your own panel widget&amp;rdquo; is a project you research for a week and then abandon. Here the plugin API was documented, the validator told me what I&amp;rsquo;d broken before anyone else had to, and installing it was one command. The distance between &amp;ldquo;I wish this existed&amp;rdquo; and &amp;ldquo;this exists and I&amp;rsquo;m using it&amp;rdquo; was days, not never.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s not the distro being magic. That&amp;rsquo;s a system that assumed from the start you might want to change it, versus systems built on the assumption you&amp;rsquo;d better not try.&lt;/p&gt;&#10;&lt;h2 id="everything-matches-because-everything-can"&gt;Everything matches, because everything can&lt;/h2&gt;&#10;&lt;p&gt;My desktop is a specific set of colors I picked. My terminal matches. My bar matches. My plugin matches, automatically, because it reads the theme rather than hardcoding anything. When I built this blog, the palette came out of my own desktop, and the banner came out of the same aesthetic.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s a stupid thing to care about. I care about it a lot. A machine that looks like something you chose rather than something you were assigned is a machine you actually want to sit down at, and I refuse to be embarrassed about that.&lt;/p&gt;&#10;&lt;h2 id="the-part-where-it-broke-everything"&gt;The part where it broke everything&lt;/h2&gt;&#10;&lt;p&gt;Now the honest part, because a post that only lists the good parts is an ad and I&amp;rsquo;m not writing one of those.&lt;/p&gt;&#10;&lt;p&gt;The GPU thing was genuinely bad. &lt;code&gt;nvidia-powerd&lt;/code&gt; never gets enabled on install, so my RTX 5060 sat pinned at 50 watts instead of boosting to 100, silently, with no error and no warning, on a laptop that had been flawless for a year prior. I only found it because I got suspicious enough to check numbers instead of trusting vibes. That&amp;rsquo;s a real bug with a real cost, and it shipped.&lt;/p&gt;&#10;&lt;p&gt;The monitor thing was the same flavor: 165Hz panel running at 60 because the config takes whatever refresh rate the monitor advertises first rather than the highest one available. Two unrelated bugs stacked on top of each other, both invisible, both making the machine feel worse than it was in ways I couldn&amp;rsquo;t immediately articulate.&lt;/p&gt;&#10;&lt;p&gt;And then I reinstalled and lost my entire blog, because I&amp;rsquo;d been building it locally without a remote. Not Omarchy&amp;rsquo;s fault in any meaningful sense. But it happened because I was reinstalling, and I was reinstalling because the install is cheap, which is exactly the freedom I just spent three paragraphs praising. Cheap experiments cut both ways.&lt;/p&gt;&#10;&lt;h2 id="why-thats-not-a-contradiction"&gt;Why that&amp;rsquo;s not a contradiction&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the thing though: I could actually fix all of it.&lt;/p&gt;&#10;&lt;p&gt;The GPU bug, I proved with a controlled experiment, masking and unmasking the daemon on two different distros until the number moved exactly where I predicted, then filed a bug report with reproduction steps clean enough that there was nothing left to argue about. The monitor bug was a config line. The lost blog got rebuilt from my own notes in fifteen minutes and now has three separate layers of backup that didn&amp;rsquo;t exist before, which is strictly better than what I had.&lt;/p&gt;&#10;&lt;p&gt;None of that is possible on a system that treats you as a consumer. When something&amp;rsquo;s broken on a sealed appliance, your options are wait, or accept it, or buy a different appliance. Here, every single problem was mine to dig into, understand, and either fix or report properly. Even the failures were interesting.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s what I actually mean by fun. Not that nothing goes wrong. Plenty goes wrong. It&amp;rsquo;s that when something does go wrong, I get to open it up and find out why, and I usually can, and sometimes the fix I send back helps someone else who would&amp;rsquo;ve hit the same wall.&lt;/p&gt;&#10;&lt;p&gt;I had forgotten computers could be like that. Somewhere between work machines I don&amp;rsquo;t control and phones I&amp;rsquo;m not allowed to open, I&amp;rsquo;d drifted into treating my own hardware as something that happens to me rather than something I do things with.&lt;/p&gt;&#10;&lt;p&gt;Thirty-nine seconds to install, an afternoon to write a plugin, a week to find a bug nobody else had pinned down. It broke my GPU and I like it anyway.&lt;/p&gt;&#10;</content:encoded></item><item><title>Zero Is a Lie</title><link>https://www.onelegdave.dev/posts/zero-is-a-lie/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/zero-is-a-lie/</guid><pubDate>Sun, 06 Sep 2026 11:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Plugins</category><category>Linux</category><description>My first published Omarchy plugin started as a lazy wrapper around btop and ended with me ripping btop out entirely. Along the way I had to decide what a system monitor is allowed to say when it genuinely does not know something.</description><media:content url="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/zero-is-a-lie.jpg" alt="Zero Is a Lie" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I wanted a system monitor in my bar that actually matched my theme instead of sitting there as a mismatched floating terminal window I had to manually position every single time I logged in. The honest first version of this project was embarrassing: wrap btop, throw it in a panel, call it done. It even shipped under that name internally. Then I actually sat with it for a week and got annoyed enough to rebuild the whole thing properly.&lt;/p&gt;&#10;&lt;p&gt;What exists now is System QuikView, a native Omarchy Shell plugin. No btop. No root service. No cloud account, no pip packages, nothing phoning home. It reads Linux telemetry directly off &lt;code&gt;/proc&lt;/code&gt; and sysfs, renders it through Quickshell&amp;rsquo;s own QML, and pulls every single color it displays from whatever Omarchy theme happens to be active. Switch themes, the widget updates live, because it was never allowed to hardcode a color to begin with.&lt;/p&gt;&#10;&lt;h2 id="the-migration-path-i-owe-past-me"&gt;The migration path I owe past me&lt;/h2&gt;&#10;&lt;p&gt;The installer still knows how to find and migrate that original btop-wrapper prototype: same placement, same preferences, straight upgrade path, no starting over. Small thing, but I&amp;rsquo;m glad I didn&amp;rsquo;t just delete the evidence and pretend the finished version sprang out fully formed. It didn&amp;rsquo;t. It started as duct tape around someone else&amp;rsquo;s tool and got replaced piece by piece until none of the original tape was left.&lt;/p&gt;&#10;&lt;h2 id="the-design-decision-that-actually-took-thought"&gt;The design decision that actually took thought&lt;/h2&gt;&#10;&lt;p&gt;Most of the surface area here is fairly mechanical: read a number, put it on a graph, color it by theme. The one part that made me stop and actually think came from GPU handling, and specifically from a question that sounds trivial until you sit with it: what does the widget show when a sensor genuinely has no reading?&lt;/p&gt;&#10;&lt;p&gt;The lazy answer is zero. Zero is wrong, and it&amp;rsquo;s wrong in a way that&amp;rsquo;s actively dangerous for exactly the same reason a blank medication list is dangerous. Zero degrees is not &amp;ldquo;no reading available,&amp;rdquo; it&amp;rsquo;s a specific, false, freezing-cold claim about a piece of hardware that might currently be on fire. A missing value and an actual value of zero are two completely different facts, and collapsing them into the same digit is a decision to lie every single time the sensor doesn&amp;rsquo;t answer.&lt;/p&gt;&#10;&lt;p&gt;So a missing sensor shows a plain dash. Not a number pretending to be data. An honest admission that nothing came back.&lt;/p&gt;&#10;&lt;p&gt;That same principle runs through the multi-GPU handling too. If you explicitly pick a specific GPU by name and it disconnects, the widget does not quietly fail over to whichever GPU happens to still be plugged in and keep drawing a graph like nothing happened. It shows that reading as unavailable and waits for you to notice, because silently substituting a different device&amp;rsquo;s numbers under the same label is its own kind of lie, just dressed up as helpfulness.&lt;/p&gt;&#10;&lt;h2 id="auto-mode-gets-to-be-smart-explicit-mode-does-not"&gt;Auto mode gets to be smart, explicit mode does not&lt;/h2&gt;&#10;&lt;p&gt;There&amp;rsquo;s a real tension between &amp;ldquo;just work&amp;rdquo; and &amp;ldquo;never guess wrong,&amp;rdquo; so I split it into two modes on purpose instead of picking one philosophy and forcing it everywhere.&lt;/p&gt;&#10;&lt;p&gt;Auto mode is allowed to be clever: prefer a dedicated GPU when one&amp;rsquo;s available and reporting, fall back to whatever else exists otherwise, and it can pick different actual devices for usage versus temperature versus VRAM if that&amp;rsquo;s genuinely what&amp;rsquo;s available. That&amp;rsquo;s a real convenience and I don&amp;rsquo;t think it&amp;rsquo;s dishonest, because nobody who picks &amp;ldquo;auto&amp;rdquo; is claiming to know or care which specific piece of silicon answers.&lt;/p&gt;&#10;&lt;p&gt;But the moment you pin a specific device by name, you&amp;rsquo;ve made a claim, and the widget&amp;rsquo;s job flips from being helpful to being accurate to that claim. It doesn&amp;rsquo;t get to quietly reinterpret &amp;ldquo;show me this GPU&amp;rdquo; as &amp;ldquo;show me a GPU&amp;rdquo; just because the first one went away. Same underlying data source, two completely different obligations depending on whether a human explicitly chose a specific thing or asked for the software&amp;rsquo;s best guess.&lt;/p&gt;&#10;&lt;h2 id="what-actually-shipped"&gt;What actually shipped&lt;/h2&gt;&#10;&lt;p&gt;Bar reading you can set to CPU, memory, GPU usage, temperature, VRAM, network speed, disk usage, or nothing but an icon. Click it, get a full dashboard: history graphs, per-process ranking by CPU then memory, temperature gauges that use the hardware&amp;rsquo;s own reported warning thresholds when it has them and fall back to a plain labeled scale when it doesn&amp;rsquo;t. Sections reorder and collapse, and that layout survives a restart instead of resetting itself every time like it&amp;rsquo;s personally offended you touched it.&lt;/p&gt;&#10;&lt;p&gt;Tests cover the boring-but-load-bearing stuff: no GPU present, one GPU, several GPUs, a GPU that vanishes mid-session, missing readings across the board. &lt;code&gt;python3 -m unittest&lt;/code&gt;, a separate Node suite for the selection logic in &lt;code&gt;Model.js&lt;/code&gt;, a QML linter, and a plugin validator Omarchy itself ships to catch structural mistakes before they become someone else&amp;rsquo;s bug report.&lt;/p&gt;&#10;&lt;p&gt;Installed like any other Omarchy plugin:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;omarchy plugin add https://github.com/onelegdave/system-quikview.git --enable&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;First one of these I&amp;rsquo;ve actually finished and put out publicly rather than leaving half-built in a folder labeled &amp;ldquo;someday.&amp;rdquo; The btop wrapper would have shipped faster. It also would have kept lying to me about GPU temperature the first time a sensor didn&amp;rsquo;t answer, and I&amp;rsquo;d rather have spent the extra week than ship a monitor I can&amp;rsquo;t actually trust the numbers on.&lt;/p&gt;&#10;</content:encoded></item><item><title>Tested. Deployed. Wrong Anyway.</title><link>https://www.onelegdave.dev/posts/tested-deployed-wrong/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/tested-deployed-wrong/</guid><pubDate>Sat, 05 Sep 2026 09:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Self-hosted</category><description>147 tests, a proven arithmetic fix, a clean deployment. Then I actually used the thing for ten minutes and found four bugs the tests were structurally incapable of seeing.</description><media:content url="https://www.onelegdave.dev/images/posts/tested-deployed-wrong.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/tested-deployed-wrong.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/tested-deployed-wrong.jpg" alt="Tested. Deployed. Wrong Anyway." width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;Same medication tracker as last time, different day, different problem. This one isn&amp;rsquo;t about the app going dark. It&amp;rsquo;s about everything technically working and still being wrong.&lt;/p&gt;&#10;&lt;p&gt;147 tests. Nearly 2,000 lines of Kotlin. Everything green. I deployed it, used it for real for about ten minutes, and found four separate ways it was lying to me, none of which a single one of those 147 tests could ever have caught, because they weren&amp;rsquo;t bugs in what the code did. They were bugs in what the running app was telling a human standing in front of it.&lt;/p&gt;&#10;&lt;h2 id="arithmetic-first-because-the-rest-doesnt-matter-if-this-is-wrong"&gt;Arithmetic first, because the rest doesn&amp;rsquo;t matter if this is wrong&lt;/h2&gt;&#10;&lt;p&gt;Before any database, any HTTP, anything, I wrote the days-remaining math as plain Kotlin with zero dependencies, because if this number&amp;rsquo;s wrong, the app isn&amp;rsquo;t useless, it&amp;rsquo;s actively dangerous. Confidently wrong about whether you&amp;rsquo;re about to run out of insulin is worse than not having an app at all.&lt;/p&gt;&#10;&lt;p&gt;The spec had it as two separate divisions to average weekly and as-needed doses onto one timeline. I collapsed both into one exact fraction, floored once, instead of doing the math in floating point:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;days_remaining = floor( units * 210 / (weekly * 30 + prn_total * 7) )&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not being clever for the sake of it. Binary floating point flat-out gets this wrong on exact boundaries: 4.5 units on hand, 4.5 used weekly, the honest answer is 7 days, and a &lt;code&gt;Double&lt;/code&gt; hands you 6. I swept roughly 56,000 schedule combinations against exact rational math to check. About 0.2% disagreed, always off by exactly one, always sitting right on a boundary. Rare enough to sail through code review unnoticed. Common enough to flip a real alert on a real person.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s now a test that asserts the naive floating-point version returns the wrong number on purpose, so future-me, or anyone else who thinks they&amp;rsquo;re &amp;ldquo;simplifying&amp;rdquo; this, finds out immediately why it&amp;rsquo;s written the ugly way.&lt;/p&gt;&#10;&lt;h2 id="breaking-my-own-tests-on-purpose"&gt;Breaking my own tests on purpose&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the practice that earned its keep hardest, and it&amp;rsquo;s embarrassingly simple: for anything a test claims to guard, break the actual mechanism on purpose and confirm the test goes red. Then put it back.&lt;/p&gt;&#10;&lt;p&gt;Sounds like paranoid busywork. It caught four tests that were passing for entirely the wrong reason, quietly guarding nothing, forever, until I forced the question.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Moved the error-catch inside the transaction.&lt;/strong&gt; Ten repeat taps took stock down to 50 instead of 59. SQLite aborts the one bad statement, not the whole transaction, so a failed deduction silently committed without its matching dose row. Three tests caught it, that&amp;rsquo;s a test doing its job.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Deleted the HTTP method guard in the service worker.&lt;/strong&gt; Every test still passed. Turns out every write case in the test suite happened to route through &lt;code&gt;/api&lt;/code&gt; and got caught by a completely different check, so that guard was dead weight nobody would&amp;rsquo;ve noticed missing until it mattered.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Deleted the offline navigation fallback.&lt;/strong&gt; Still green. The tests requested &lt;code&gt;/&lt;/code&gt;, which is precached and always resolves fine, but Android tacks a query string onto &lt;code&gt;start_url&lt;/code&gt; on a real home-screen launch, and cache matching compares the full URL including that string. A real phone launch would&amp;rsquo;ve missed the cache entirely and gotten nothing, while every single test sailed past it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Disabled the SQLite foreign-key pragma.&lt;/strong&gt; Exactly one test failed, which is the correct outcome, and the one I actually wanted to see. Foreign keys are off by default in SQLite, enabled per-connection, and any connection that forgets the pragma just quietly accepts orphaned rows forever.&lt;/p&gt;&#10;&lt;p&gt;Two of those four had the actual mechanism removed and the suite stayed green. A green test suite proves the tests ran. It proves nothing about the code until you&amp;rsquo;ve personally watched each one fail for the reason it claims to exist.&lt;/p&gt;&#10;&lt;h2 id="things-only-the-real-server-was-rude-enough-to-tell-me"&gt;Things only the real server was rude enough to tell me&lt;/h2&gt;&#10;&lt;p&gt;Ran clean locally. Deployed to the actual box and immediately hit three problems no amount of local testing was ever going to surface.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;A tmpfs mounted &lt;code&gt;noexec&lt;/code&gt;.&lt;/strong&gt; First run died instantly: &lt;code&gt;UnsatisfiedLinkError: failed to map segment from shared object&lt;/code&gt;. The SQLite driver extracts its native library to the JVM temp directory and loads it from there. Docker mounts &lt;code&gt;--tmpfs noexec&lt;/code&gt; by default, so the load just failed on the very first database open. The error message says nothing whatsoever about mount flags. &lt;code&gt;/tmp&lt;/code&gt; is now mounted exec, with a comment explaining exactly why, because the &amp;ldquo;obvious&amp;rdquo; future hardening move is a crash loop.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;A port that was already spoken for.&lt;/strong&gt; Installing to a phone needs a service worker, which needs a secure context: HTTPS or bust. Plain HTTP over the tailnet serves the app fine and just silently never offers to install, no warning. So: real cert via Tailscale Serve, which defaults to 443, except 443 on that box was already doing something completely unrelated. Publishing there would&amp;rsquo;ve shadowed an existing service&amp;rsquo;s cert and taken it down as a side effect of deploying a medication app, which is the kind of blast radius nobody signs up for on purpose. Moved to a different port. Costs nothing, a secure context cares about scheme, not port number.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Compose stacks that fail without telling anyone.&lt;/strong&gt; On that server, containers aren&amp;rsquo;t wrapped in systemd units, they lean entirely on Docker&amp;rsquo;s own restart policy. Which means a crash-looping container restarts forever, silently, and nothing ever reports it. I&amp;rsquo;d written that exact fact down months earlier and completely forgotten I had. A dead media server is obvious the second you try to watch something. A dead medication tracker is invisible until the specific morning you go to check it and it isn&amp;rsquo;t there.&lt;/p&gt;&#10;&lt;p&gt;Fix ended up being the nightly low-stock alert script, since that one &lt;em&gt;does&lt;/em&gt; run as a proper systemd timer with real failure notification wired up, but only if it&amp;rsquo;s built to actually notice a dead server instead of politely assuming an empty response means everything&amp;rsquo;s fine:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;ALERTS=&amp;#34;$(curl -fsS --max-time 20 &amp;#34;$API/api/alerts&amp;#34;)&amp;#34;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That &lt;code&gt;-f&lt;/code&gt; flag is doing the entire job. Without it, a dead container hands back an error page, the script parses zero alerts out of it, and reports &amp;ldquo;all clear&amp;rdquo; every single night the app is down. Silence dressed up as reassurance. The gap between a real alerting system and one that just resembles one was a single missing character in a shell script.&lt;/p&gt;&#10;&lt;p&gt;I didn&amp;rsquo;t just trust that. Stopped the container, ran the check by hand, watched it die with exit 7, watched systemd catch the failure, watched the notification land on my phone. Good time to prove that chain: database was still empty.&lt;/p&gt;&#10;&lt;h2 id="what-ten-minutes-of-actually-using-it-found"&gt;What ten minutes of actually using it found&lt;/h2&gt;&#10;&lt;p&gt;Everything above, tested and proven. Then I installed it for real, put in my actual medications, and tapped &amp;ldquo;take all&amp;rdquo; for the morning. Four bugs inside ten minutes, and not one of them was about what the code computed. Every single one was about what the app told a person looking at the screen.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Offline hung instead of failing.&lt;/strong&gt; Airplane mode, app opens to its splash screen, and just&amp;hellip; stays there. Forever. No app, no error, nothing. An unreachable server on a tailnet doesn&amp;rsquo;t always politely refuse the connection. The name still resolves, nothing rejects the socket, the fetch just never resolves either way. My service worker was awaiting it with zero timeout, so the whole page had nothing to render and nothing to say. Every network call now has an actual number on it. Reads give up after 4 seconds since someone&amp;rsquo;s standing there waiting and a read is cheap to retry, writes get 8 because a timed-out write might have landed and the only honest move is to make you go check.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;A blank screen making a claim it hadn&amp;rsquo;t earned.&lt;/strong&gt; With the hang fixed, it correctly showed a red &amp;ldquo;can&amp;rsquo;t reach server&amp;rdquo; banner, sitting directly above a completely empty medication list. Which is the single worst thing this specific app is allowed to display, because an empty list and &amp;ldquo;nothing due today&amp;rdquo; are visually identical, and one of those two things at six in the morning is dangerous to get wrong. Fixed: no list is ever blank by default now. Loading says loading. Failure says failure, explicitly, as &amp;ldquo;can&amp;rsquo;t reach the server,&amp;rdquo; never silently rendered as an empty day.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Doses landing on the wrong calendar day.&lt;/strong&gt; Tapped &amp;ldquo;take all,&amp;rdquo; and it logged against yesterday. Today then cheerfully offered to let me take everything again, since as far as it could tell, nothing had happened yet. The write itself was fine, stock even decremented correctly, the client had simply sent the wrong date. I never fully pinned down how it drifted onto the wrong day, so the fix targets the whole category rather than one cause: a loud banner when you&amp;rsquo;re not viewing today, a confirmation before bulk-logging against a past date, and the fix that&amp;rsquo;ll actually matter, following the real date across a midnight rollover on resume, since this is an app that gets opened right before bed and first thing in the morning.&lt;/p&gt;&#10;&lt;p&gt;The fourth bug that day was the app going completely dark on every device while the server was perfectly healthy. That one got its own post, &lt;a href="https://www.onelegdave.dev/posts/server-never-down/"&gt;The Server Was Never Down&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="the-default-i-refused-to-ship"&gt;The default I refused to ship&lt;/h2&gt;&#10;&lt;p&gt;Last thing that came out of actually using it: an as-needed medication was projecting &lt;strong&gt;690 days remaining&lt;/strong&gt; on a bottle of ibuprofen. Technically derived correctly from the math. Read as complete nonsense the second a human looked at it, because a rate based on whatever happened to occur in the last thirty days swings wildly on a single dose and dresses that swing up as precision it doesn&amp;rsquo;t have.&lt;/p&gt;&#10;&lt;p&gt;Fix: as-needed medications alert on raw units left instead of a days projection. That also patched a hole nobody had noticed. Under the days-based rule, an as-needed medication with nothing logged in the past month has no projection &lt;em&gt;at all&lt;/em&gt;, and a null projection never alerts, so it could sit at two tablets left and say absolutely nothing about it.&lt;/p&gt;&#10;&lt;p&gt;The migration wanted a sensible default threshold backfilled onto every existing as-needed medication, ten units seemed reasonable. Checked it against the real data before shipping it, because &amp;ldquo;seemed reasonable&amp;rdquo; is not a threshold, it&amp;rsquo;s a guess wearing a lab coat:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Gabapentin 64 units&#10;Lispro (Insulin) 1385 units &amp;lt;- 10-unit threshold&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A ten-unit warning on 1385 units of insulin would functionally never fire, on the exact medication my own original spec singled out by name as the one where running short is not a minor inconvenience. Ten&amp;rsquo;s fine for a bottle of tablets and dangerously wrong for that. There is no single default that&amp;rsquo;s safe across both, so the migration adds the column and backfills nothing. Existing meds keep their days-based threshold until a real number gets chosen for each one, and the reasoning&amp;rsquo;s written directly into the migration file so nobody &amp;ldquo;helpfully&amp;rdquo; finishes the job for me later without reading why it was left undone.&lt;/p&gt;&#10;&lt;p&gt;Worth naming the general shape of that mistake: a default is a decision made on someone else&amp;rsquo;s behalf without knowing their actual situation. Usually that&amp;rsquo;s a harmless convenience. The moment that number is the thing deciding whether a warning fires at all, it stops being convenient and starts being a bet you didn&amp;rsquo;t ask permission to make.&lt;/p&gt;&#10;&lt;h2 id="breaking-my-own-rule-on-purpose-in-writing"&gt;Breaking my own rule, on purpose, in writing&lt;/h2&gt;&#10;&lt;p&gt;That change directly contradicted a line in my own spec, filed under a heading that literally said &lt;em&gt;settled, do not reopen during implementation&lt;/em&gt;. That rule exists for a good reason: it&amp;rsquo;s there to stop you relitigating design decisions mid-build out of boredom or anxiety.&lt;/p&gt;&#10;&lt;p&gt;But that rule was written before the thing existed, and actually running it produced evidence that plain didn&amp;rsquo;t exist at the time I wrote the rule down. So I reopened it anyway, and documented in the spec itself that it was reopened, when, and specifically why, instead of just quietly contradicting a document I&amp;rsquo;d told myself not to touch. The original math was never wrong. It was unhelpful in a way you can only discover from the far side of a system that actually runs.&lt;/p&gt;&#10;&lt;p&gt;147 tests, all green, the whole time. None of them were ever going to catch a number that was correct and useless, a screen that was blank and honest at the same time, or a default that was reasonable for one drug and reckless for another. That&amp;rsquo;s not a gap in the test suite. It&amp;rsquo;s the actual, permanent boundary of what a test suite can tell you: it verifies what code does. Only running the thing in front of an actual person tells you what it means to them.&lt;/p&gt;&#10;</content:encoded></item><item><title>The Server Was Never Down</title><link>https://www.onelegdave.dev/posts/server-never-down/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/server-never-down/</guid><pubDate>Fri, 04 Sep 2026 10:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Self-hosted</category><description>A medication tracker I built for myself started refusing to connect, from every device, while the server sat there perfectly healthy the entire time. The bug was a ghost, and I built it myself months earlier without noticing.</description><media:content url="https://www.onelegdave.dev/images/posts/server-never-down.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/server-never-down.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/server-never-down.jpg" alt="The Server Was Never Down" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I built myself a medication tracker. Single user, me, running on my own server, reachable only over my own private network. Not a health app in the App Store sense: no dose advice, no interactions, no clinical logic anywhere in it. It counts what I have, tells me what&amp;rsquo;s due, and yells at me before I run out. That&amp;rsquo;s the entire scope, on purpose, because the second a personal project starts pretending to be a doctor is the second it becomes a fucking liability instead of a tool.&lt;/p&gt;&#10;&lt;p&gt;I finished it. Deployed it. Started actually using it for real medications, which is the part that matters. A supply tracker that only ever gets tested with fake data hasn&amp;rsquo;t actually been tested for shit. And within days it started telling me it couldn&amp;rsquo;t reach the server. From my phone. From my desktop. Every device, same error, connection refused, like the whole thing had just died in the night.&lt;/p&gt;&#10;&lt;p&gt;The server had not died in the night. The server had never been healthier. I was staring at a goddamn ghost.&lt;/p&gt;&#10;&lt;h2 id="guilty-until-proven-guilty"&gt;Guilty until proven guilty&lt;/h2&gt;&#10;&lt;p&gt;First instinct, obviously: check the server. It was fine. Container healthy, every endpoint answering, curl from a completely different device on the network getting clean 200s back like nothing was wrong, because nothing fucking was.&lt;/p&gt;&#10;&lt;p&gt;Second instinct: check the network. Also fine. Nothing had changed there in weeks.&lt;/p&gt;&#10;&lt;p&gt;Third instinct, the one that should embarrass me a little: assume the phone was lying, restart it, uninstall the app, reinstall it from the home screen, watch it fail exactly the same way, immediately, with total confidence, like it had done this before.&lt;/p&gt;&#10;&lt;p&gt;It had done this before. It had done this every single time, because the thing I was reinstalling wasn&amp;rsquo;t actually broken. It was faithfully, perfectly doing exactly what a Progressive Web App is designed to do, and the design was the fucking problem.&lt;/p&gt;&#10;&lt;h2 id="the-ghost-was-mine"&gt;The ghost was mine&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the part that stung once I found it: a PWA doesn&amp;rsquo;t get installed from an app store with a name and a version number. It gets installed from a URL, and it stays bound to that exact origin forever. Not the app. The address. Reinstalling doesn&amp;rsquo;t fix a stale origin, it just re-commits to the same dead one, because as far as the phone&amp;rsquo;s concerned that origin &lt;em&gt;is&lt;/em&gt; the app&amp;rsquo;s whole identity.&lt;/p&gt;&#10;&lt;p&gt;Months earlier, before there was anywhere real to deploy this thing, I&amp;rsquo;d stood up a quick throwaway address on my laptop just to test how the layout looked on an actual phone screen instead of squinting at a browser window pretending to be one. Totally reasonable thing to do. Tore it down the moment real deployment happened, also totally reasonable. Except at some point during that testing, I&amp;rsquo;d installed the damn thing to my home screen from that scratch address, and never thought about it again, like a dumbass.&lt;/p&gt;&#10;&lt;p&gt;So the icon on my phone was never talking to the server. It had never once talked to the server. It had been faithfully, patiently trying to reach a machine that stopped listening for that address weeks ago, and every single time it failed, it failed with an error that is byte-for-byte identical to &amp;ldquo;the server is down.&amp;rdquo; &lt;code&gt;ERR_CONNECTION_REFUSED&lt;/code&gt; doesn&amp;rsquo;t editorialize. It doesn&amp;rsquo;t say &amp;ldquo;hey, this domain doesn&amp;rsquo;t exist anymore, you absolute clown, might want to check that.&amp;rdquo; It just refuses, flatly, and leaves you to guess why.&lt;/p&gt;&#10;&lt;p&gt;Fixed by pointing the install at the actual live address and pinning that URL somewhere obnoxiously obvious so future-me doesn&amp;rsquo;t get to relive this shit.&lt;/p&gt;&#10;&lt;h2 id="what-the-silence-was-hiding"&gt;What the silence was hiding&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s where it stopped being embarrassing and started being useful. The server had no access log. None. Which meant when this started, I had zero way to tell the difference between &amp;ldquo;the phone tried to connect and got rejected&amp;rdquo; and &amp;ldquo;the phone never tried at all.&amp;rdquo; Those are completely different failures with completely different fixes, and I couldn&amp;rsquo;t tell them apart because I&amp;rsquo;d never built the thing that would let me.&lt;/p&gt;&#10;&lt;p&gt;So I added one. And the very first thing it proved was that every single entry in it, for the entire time this had supposedly been &amp;ldquo;down,&amp;rdquo; was the container&amp;rsquo;s own healthcheck pinging itself every thirty seconds like clockwork. Nothing else. No failed request. No rejected connection. Not one single byte from my phone had ever arrived, because it was never being sent anywhere near this fucking machine in the first place.&lt;/p&gt;&#10;&lt;p&gt;That log (something I only built because I got burned) is also what turned up two real bugs hiding underneath the fake one, which is exactly what happens when you finally go looking properly instead of vibing it. The app&amp;rsquo;s reconnection logic was trusting the browser&amp;rsquo;s &lt;code&gt;online&lt;/code&gt; event to know when the network came back, and that event is basically decorative on a phone, so a genuinely brief network drop could get treated as permanent, forever, until you manually intervened like an idiot. And separately, when the app &lt;em&gt;did&lt;/em&gt; recover, it only bothered refreshing two of its three screens and left a stale &amp;ldquo;can&amp;rsquo;t reach server&amp;rdquo; panel sitting on the third, so even a fully working reconnect still looked broken if you happened to glance at the wrong tab.&lt;/p&gt;&#10;&lt;p&gt;Neither of those caused the outage. Both would have made a &lt;em&gt;real&lt;/em&gt; outage worse, invisibly, and I only found them because a fake one forced me to build the tooling to actually see what was happening instead of guessing at it.&lt;/p&gt;&#10;&lt;h2 id="the-pattern-underneath-all-of-it"&gt;The pattern underneath all of it&lt;/h2&gt;&#10;&lt;p&gt;I&amp;rsquo;d already built this thing around one rule, from the start, mostly out of paranoia: nothing in this app is allowed to be blank or silent by accident, because for a medication tracker, blank and silent both read as &amp;ldquo;nothing to worry about,&amp;rdquo; and that&amp;rsquo;s exactly the one lie this app is not allowed to tell, ever. An empty list has to mean the server confirmed there&amp;rsquo;s nothing due, never &amp;ldquo;the request hasn&amp;rsquo;t come back yet&amp;rdquo; wearing an empty list&amp;rsquo;s clothes like a coward.&lt;/p&gt;&#10;&lt;p&gt;This whole outage was that exact philosophy failing at a layer I hadn&amp;rsquo;t built it into yet. The client had no way to say &amp;ldquo;I am not even reaching the network you think I am,&amp;rdquo; so it said nothing, and nothing looks exactly like everything being fine, which looks exactly like everything being fucked, depending on which side of the silence you&amp;rsquo;re standing on. A dead request and a healthy one that just hasn&amp;rsquo;t finished look identical if you never bother asking which one you&amp;rsquo;re actually looking at.&lt;/p&gt;&#10;&lt;p&gt;Turns out the same rule that governs whether an empty dose list can be trusted also governs whether &amp;ldquo;can&amp;rsquo;t connect&amp;rdquo; means what you think it means. Silence is never neutral. It&amp;rsquo;s just a claim wearing a disguise, and the whole job is making sure the disguise never fucking fits.&lt;/p&gt;&#10;</content:encoded></item><item><title>Half a GPU</title><link>https://www.onelegdave.dev/posts/half-a-gpu/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/half-a-gpu/</guid><pubDate>Wed, 02 Sep 2026 14:00:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Linux</category><category>Omarchy</category><description>My laptop GPU was quietly running at half power on Omarchy, after a full year of this exact machine giving me zero problems. Took less than a week to notice, and I still almost talked myself out of it.</description><media:content url="https://www.onelegdave.dev/images/posts/half-a-gpu.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/half-a-gpu.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/half-a-gpu.jpg" alt="Half a GPU" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;I bought a laptop with an RTX 5060 in it a year ago. It has been, until recently, completely unremarkable: no weird crashes, no thermal bullshit, no gremlins. A full year of just fucking working. Then I put Omarchy on it, and in under a week I owned a laptop with half an RTX 5060 in it, and didn&amp;rsquo;t know.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the thing about a hobbled GPU: it doesn&amp;rsquo;t announce itself. It doesn&amp;rsquo;t throw an error. It just quietly does less work than it&amp;rsquo;s capable of, while you sit there thinking &amp;ldquo;huh, this feels a bit sluggish for the hardware&amp;rdquo; and blaming everything except the actual cause, because the actual cause is invisible unless you go looking for it with a multimeter&amp;rsquo;s worth of paranoia.&lt;/p&gt;&#10;&lt;h2 id="the-vibe-was-off-fast"&gt;The vibe was off, fast&lt;/h2&gt;&#10;&lt;p&gt;This is the part that annoys me most in hindsight: this is a machine with a full year of clean behavior on record. Same laptop, same silicon, same everything, running perfectly fine on a previous install for twelve straight months. Then a new distro goes on, and within days something feels off. The correct instinct there is obvious: suspect the thing you just changed, not the hardware that&amp;rsquo;s been flawless for a year. I did not have this instinct on day one. I had it by about day four, after several days of increasingly irritated vague dissatisfaction.&lt;/p&gt;&#10;&lt;p&gt;New distro, new install, everything looking gorgeous, performance feeling&amp;hellip; fine. Not bad. Just fine. Games ran. Renders rendered. Nothing was on fire. Which is exactly the problem with a silent performance regression: there&amp;rsquo;s no crash to point at, no log line screaming for attention, just a persistent, low-grade sense that something&amp;rsquo;s leaving performance on the table and you can&amp;rsquo;t fucking prove it. So you sit there for days quietly resenting your own laptop like an idiot, forgetting it had been perfect for a year right up until you changed the one thing you changed less than a week ago.&lt;/p&gt;&#10;&lt;p&gt;So I did what any reasonable person does when they can&amp;rsquo;t prove a feeling: I went and got numbers.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; while gaming. Power draw sitting at a suspiciously round &lt;strong&gt;50 watts&lt;/strong&gt;, pinned there like a coward. Not &amp;ldquo;50 watts because the game is light on the GPU.&amp;rdquo; Fifty watts &lt;em&gt;flat&lt;/em&gt;, under a load that should have been begging for more.&lt;/p&gt;&#10;&lt;p&gt;For context: this is a laptop 5060, and 50W is roughly its baseline TGP. That&amp;rsquo;s the floor, not the ceiling. These chips are supposed to boost well above that under load. Sitting exactly at the floor, permanently, isn&amp;rsquo;t &amp;ldquo;conservative,&amp;rdquo; it&amp;rsquo;s &amp;ldquo;someone forgot to flip a fucking switch.&amp;rdquo;&lt;/p&gt;&#10;&lt;h2 id="proving-it-wasnt-in-my-head"&gt;Proving it wasn&amp;rsquo;t in my head&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s where it stopped being a vibe and became an actual investigation, because &amp;ldquo;the GPU feels capped&amp;rdquo; is not a bug report anyone can act on, including me.&lt;/p&gt;&#10;&lt;p&gt;I had the same physical laptop running CachyOS not long before this, for that entire clean year. Different distro, different config, identical silicon, a full year of prior history saying this hardware has zero problems. So I had something no bug report ever gets: a perfect A/B test on the same chip.&lt;/p&gt;&#10;&lt;p&gt;Booted CachyOS. Same game, same settings, same everything. &lt;code&gt;nvidia-smi&lt;/code&gt; again.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;85 to 93 watts, 78 to 80°C under load.&lt;/strong&gt; Same chip. Same cooler. Same room temperature. Nearly double the power draw, just from being a different pile of software sitting on top of it.&lt;/p&gt;&#10;&lt;p&gt;So it wasn&amp;rsquo;t the silicon. It wasn&amp;rsquo;t age. It wasn&amp;rsquo;t thermal throttling. It wasn&amp;rsquo;t my imagination, my expectations, or a shit game engine. It was Omarchy specifically doing something (or rather, not doing something) that CachyOS was doing right, and that this exact laptop had apparently been quietly capable of for a year without complaint.&lt;/p&gt;&#10;&lt;h2 id="finding-the-leash"&gt;Finding the leash&lt;/h2&gt;&#10;&lt;p&gt;The actual culprit, once I went hunting: &lt;strong&gt;&lt;code&gt;nvidia-powerd&lt;/code&gt;&lt;/strong&gt;, a background service Nvidia ships specifically to manage Dynamic Boost, the mechanism that lets the GPU actually claim higher power states under load instead of sitting at its conservative default. CachyOS enables it automatically on install. Omarchy just&amp;hellip; doesn&amp;rsquo;t. No warning, no prompt, no &amp;ldquo;hey, you might want this.&amp;rdquo; It simply isn&amp;rsquo;t part of the install, so the GPU spends its entire life politely capped at whatever the silicon defaults to out of the box, and nobody bothers to tell you.&lt;/p&gt;&#10;&lt;p&gt;I didn&amp;rsquo;t want to believe a missing background daemon was really the whole fucking story, so I did the only thing that would actually settle it: I broke the &lt;em&gt;working&lt;/em&gt; install on purpose. Masked &lt;code&gt;nvidia-powerd&lt;/code&gt; on CachyOS, the one that had been running fine for a year.&lt;/p&gt;&#10;&lt;p&gt;Power draw dropped to exactly 50 watts. Same number, same ceiling, same everything as Omarchy.&lt;/p&gt;&#10;&lt;p&gt;Then I went back to Omarchy and forced the daemon on by hand.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;100 watts.&lt;/strong&gt; Unlocked, on command, on the exact install that had spent less than a week quietly ripping me off while I sat there blaming a laptop that had done nothing wrong for a year.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s as close to a controlled experiment as you get outside an actual lab: flip one service off, lose the performance. Flip it on, get it back. Do it in both directions, on both distros, and watch the number move exactly where you told it to. At that point it&amp;rsquo;s not a theory anymore, it&amp;rsquo;s just a fact you&amp;rsquo;ve now personally reproduced four times.&lt;/p&gt;&#10;&lt;h2 id="the-compounding-bug-that-made-it-so-much-worse"&gt;The compounding bug that made it so much worse&lt;/h2&gt;&#10;&lt;p&gt;While I was in there, I found a second, completely unrelated problem stacked right on top, because apparently the universe wanted this diagnosis to be as fucking annoying as possible.&lt;/p&gt;&#10;&lt;p&gt;My external monitor does 165Hz. Omarchy had it running at 60Hz. Not because of the wattage nonsense. Because its config picks whatever refresh rate the monitor &lt;em&gt;advertises first&lt;/em&gt; in its own list, rather than the highest one available, and apparently this monitor&amp;rsquo;s firmware lists 60Hz before 165Hz for reasons known only to whoever wrote its EDID data at 2am on a Friday and never thought about it again.&lt;/p&gt;&#10;&lt;p&gt;So for a good chunk of that week I was troubleshooting &amp;ldquo;why does everything feel underpowered&amp;rdquo; while actually looking at two completely independent bugs at once: a genuinely underpowered GPU, and a display quietly running at over a third of its actual refresh rate. Neither one caused the other. Both made the machine feel like garbage, which was extra insulting given it had been flawless for a year before this install existed. Untangling &amp;ldquo;is this the wattage or is this the refresh rate&amp;rdquo; took longer than either fix did once I&amp;rsquo;d actually separated the two idiots living in the same house.&lt;/p&gt;&#10;&lt;h2 id="filing-the-report"&gt;Filing the report&lt;/h2&gt;&#10;&lt;p&gt;Once I had reproducible numbers in both directions, I wrote it up properly and filed it against Omarchy&amp;rsquo;s own tracker on GitHub. Exact steps to reproduce, exact wattage numbers, the masking test, all of it. Not &amp;ldquo;this feels slow,&amp;rdquo; but &amp;ldquo;do X, get 50W, do Y, get 100W, here&amp;rsquo;s the log,&amp;rdquo; cross-linked to the existing discussion thread so nobody has to go hunting for context.&lt;/p&gt;&#10;&lt;p&gt;A bug report that says &amp;ldquo;performance seems bad&amp;rdquo; gets ignored, correctly, because it&amp;rsquo;s unfalsifiable and everyone who&amp;rsquo;s ever run a helpdesk has learned to tune it the fuck out. A bug report that says &amp;ldquo;here is a lever, here is what happens when you pull it, twice, in both directions&amp;rdquo; gets fixed, because there&amp;rsquo;s nothing left to argue with.&lt;/p&gt;&#10;&lt;h2 id="the-actual-takeaway"&gt;The actual takeaway&lt;/h2&gt;&#10;&lt;p&gt;Modern hardware doesn&amp;rsquo;t fail loud anymore. It fails by quietly doing less than it&amp;rsquo;s capable of and daring you to notice, and it can do it in under a week flat if you let it. When a machine that&amp;rsquo;s been rock solid for a year suddenly feels off right after you changed exactly one thing, trust that math immediately. Don&amp;rsquo;t spend four days doubting a laptop that&amp;rsquo;s earned better than that.&lt;/p&gt;&#10;&lt;p&gt;Check anyway. Pull the lever. Watch the number move.&lt;/p&gt;&#10;</content:encoded></item><item><title>Whoopsie</title><link>https://www.onelegdave.dev/posts/whoopsie/</link><guid isPermaLink="true">https://www.onelegdave.dev/posts/whoopsie/</guid><pubDate>Tue, 01 Sep 2026 17:30:00 -0700</pubDate><dc:creator>OneLegDave</dc:creator><category>Linux</category><category>Omarchy</category><description>I deleted my own website, went looking for a backup, and found a beautifully preserved copy of my terminal color scheme instead.</description><media:content url="https://www.onelegdave.dev/images/posts/whoopsie.jpg" medium="image" type="image/jpeg" width="1200" height="630"/><media:thumbnail url="https://www.onelegdave.dev/images/posts/whoopsie.jpg" width="1200" height="630"/><content:encoded>&lt;p&gt;&lt;img src="https://www.onelegdave.dev/images/posts/whoopsie.jpg" alt="Whoopsie" width="1200" height="630"&gt;&lt;/p&gt;&lt;p&gt;So I built this whole site the other day. Hugo, PaperMod, a custom stylesheet with colors I sampled straight out of the banner image, a hero banner that behaves itself when you shrink the window. Took hours. Committed it to git twice, felt very pleased with myself, went to bed.&lt;/p&gt;&#10;&lt;p&gt;Then I reinstalled my distro.&lt;/p&gt;&#10;&lt;p&gt;You already know how this ends.&lt;/p&gt;&#10;&lt;h2 id="the-part-where-i-find-out"&gt;The part where I find out&lt;/h2&gt;&#10;&lt;p&gt;Fresh install, home directory looked fine. My notes vault was there. Pictures were there. Downloads, full of the usual crap, also there. My Projects folder was sitting right where I left it, containing exactly two things, both belonging to a different project entirely.&lt;/p&gt;&#10;&lt;p&gt;No blog.&lt;/p&gt;&#10;&lt;p&gt;I ran a &lt;code&gt;find&lt;/code&gt; across the entire filesystem for the project directory, because denial is a process and I wanted to watch it return nothing before I&amp;rsquo;d accept it. It returned nothing. Beautiful. Very quick about it, too.&lt;/p&gt;&#10;&lt;p&gt;Then I remembered the migration snapshots on my server. Three of them, taken at various points when I&amp;rsquo;ve moved this laptop between distros like a man who cannot commit to anything. Surely one of those had it.&lt;/p&gt;&#10;&lt;p&gt;The most recent one, taken &lt;em&gt;right before this reinstall&lt;/em&gt;, contained: Desktop, Documents, my shell config, my terminal config, and Pictures. No Projects. Fantastic. Really glad I saved my color scheme.&lt;/p&gt;&#10;&lt;p&gt;The one before that did have a Projects folder. My heart did a thing. I opened it. Same two unrelated projects, no blog, because that snapshot predates the blog by weeks. So the site was born and died in the gap between two backups, which is a hell of a way to go.&lt;/p&gt;&#10;&lt;h2 id="but-you-used-git"&gt;&amp;ldquo;But you used git&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;I did use git. Two clean commits, nice descriptive messages, branch named &lt;code&gt;main&lt;/code&gt; like a well-adjusted adult who has his shit together.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the thing about a local git repo that nobody enjoys learning the hard way: the entire history lives in a &lt;code&gt;.git&lt;/code&gt; folder &lt;em&gt;inside the project directory&lt;/em&gt;. Delete the project, delete the history. Git isn&amp;rsquo;t a backup. Git is a very good undo button that lives in the same building as the thing it&amp;rsquo;s undoing. Push it somewhere else or it&amp;rsquo;s just a diary you keep in the glove compartment of a car you&amp;rsquo;re about to set on fire.&lt;/p&gt;&#10;&lt;p&gt;Setting up a remote was literally on my todo list from that session. It was item two. I got to item zero.&lt;/p&gt;&#10;&lt;p&gt;The banner image went too, since that was sitting in Downloads, and Downloads is where files go to be deleted by a man doing some spring cleaning at midnight who is absolutely certain he doesn&amp;rsquo;t need any of this.&lt;/p&gt;&#10;&lt;h2 id="what-actually-protected-me-fuck-all"&gt;What actually protected me: fuck all&lt;/h2&gt;&#10;&lt;p&gt;Let&amp;rsquo;s be honest about the failure. It wasn&amp;rsquo;t bad luck. My server has a genuinely decent backup setup: Restic running twice, once to an external drive and once offsite to Backblaze B2, 7 daily / 4 weekly / 12 monthly retention, integrity checks, failure notifications straight to my phone. It&amp;rsquo;s good. I&amp;rsquo;m proud of it. I&amp;rsquo;d show it to people at parties if I went to parties.&lt;/p&gt;&#10;&lt;p&gt;It backs up &lt;code&gt;/srv/appdata&lt;/code&gt;, &lt;code&gt;/srv/sync&lt;/code&gt;, &lt;code&gt;/srv/backup&lt;/code&gt;, and my compose files. All of which live &lt;em&gt;on the server&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;The laptop was contributing precisely fuck all to that. Every &amp;ldquo;backup&amp;rdquo; of my laptop was me, by hand, going &amp;ldquo;hmm, I should probably copy some folders before I nuke this thing,&amp;rdquo; and then copying whichever folders I happened to think of in that moment. Which is exactly why I have a pristine copy of my terminal theme and no copy of my actual work.&lt;/p&gt;&#10;&lt;p&gt;Manual backups back up whatever you remembered. Not whatever mattered. Those are very different lists and you only find out how different at the worst possible moment.&lt;/p&gt;&#10;&lt;h2 id="the-fix"&gt;The fix&lt;/h2&gt;&#10;&lt;p&gt;The elegant part is that I didn&amp;rsquo;t need any new tooling. Syncthing was already running on both machines, already talking to each other, already doing its job for other folders. So:&lt;/p&gt;&#10;&lt;p&gt;My Projects folder is now a synced Syncthing folder, landing at &lt;code&gt;/srv/sync/projects&lt;/code&gt; on the server. That path is &lt;em&gt;already inside both Restic jobs&lt;/em&gt;. So the moment a file hits Projects on the laptop, it replicates to the server, and the next Restic run sweeps it into the local repo and B2. Nothing to configure in the backup scripts. It just inherited the entire chain.&lt;/p&gt;&#10;&lt;p&gt;Staggered file versioning is on at both ends, because plain Syncthing is a mirror, not a backup. Delete a file here and it will cheerfully, instantly delete it there, like an over-eager intern. Versioning means the server keeps the old copy in &lt;code&gt;.stversions&lt;/code&gt; instead of enthusiastically helping me destroy my own work.&lt;/p&gt;&#10;&lt;p&gt;One gotcha, and it cost me a genuinely confusing five minutes: Syncthing on my server runs in Docker, with &lt;code&gt;/srv/sync&lt;/code&gt; mounted as &lt;code&gt;/var/syncthing/data&lt;/code&gt; inside the container. So when I typed the host path into the web UI, it threw &lt;code&gt;mkdir /srv/sync: permission denied&lt;/code&gt; and refused to start the folder. It wasn&amp;rsquo;t a permissions problem at all. That path simply does not exist from inside the container. You have to give it the container path. Obvious in hindsight. Deeply fucking irritating at the time.&lt;/p&gt;&#10;&lt;p&gt;And finally, the thing I should have done first, before any of the rest of it: a bare git repo on the server, sitting inside a path Restic already backs up. &lt;code&gt;git push&lt;/code&gt; now sends my history somewhere that isn&amp;rsquo;t this laptop, which is the entire point of a remote and something I will not be forgetting again.&lt;/p&gt;&#10;&lt;h2 id="rebuilding"&gt;Rebuilding&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the one genuinely smart thing I did last session: at the end of it, I wrote the whole setup up properly. Every CSS variable with its hex value. The full config file. The exact ImageMagick command. The layout override trick and &lt;em&gt;why&lt;/em&gt; it works. All of it in my notes vault, which, being a synced folder, survived without a scratch.&lt;/p&gt;&#10;&lt;p&gt;Rebuild took about fifteen minutes. Install Hugo, scaffold the site, add the theme submodule, paste three files in, done.&lt;/p&gt;&#10;&lt;p&gt;The thing that took hours the first time was never the typing. It was the working out: the hunting through theme files, the trial and error, the six attempts at centering a div. Working out doesn&amp;rsquo;t need to be redone if you wrote it down. Typing is cheap. Figuring it out again is not.&lt;/p&gt;&#10;&lt;p&gt;The only real casualty was the banner. I had to re-source it, and the copy I got back is 1855px wide instead of the original 3710px. Not the end of the world. I dropped the CSS max-width to match, so it isn&amp;rsquo;t being upscaled and going soft on big monitors. Half the resolution, none of the blur. You&amp;rsquo;d never know unless I told you, which I just did, because apparently I can&amp;rsquo;t help myself.&lt;/p&gt;&#10;&lt;h2 id="the-lesson-such-as-it-is"&gt;The lesson, such as it is&lt;/h2&gt;&#10;&lt;p&gt;Backups you have to remember to run are not backups. They&amp;rsquo;re a hobby you do occasionally, and the gap between &amp;ldquo;when I last felt like it&amp;rdquo; and &amp;ldquo;right now&amp;rdquo; is precisely where your work goes to die.&lt;/p&gt;&#10;&lt;p&gt;Automate it or accept that you&amp;rsquo;re going to lose something eventually. There&amp;rsquo;s no third option where you&amp;rsquo;re just really diligent about it forever, no matter how much you&amp;rsquo;d like there to be. I thought I was that guy. I was not that guy.&lt;/p&gt;&#10;&lt;p&gt;Anyway. Site&amp;rsquo;s back. It&amp;rsquo;s now backed up three separate ways, which feels like wild overkill until you remember I&amp;rsquo;m the same idiot who deleted it in the first place.&lt;/p&gt;&#10;</content:encoded></item></channel></rss>