<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>daal.cloud - articles</title>
  <subtitle>Vince van Daal&#39;s retro portal - client-side browser tools and articles on Linux, infra, AWS, and working with AI.</subtitle>
  <link href="https://daal.cloud/articles/feed.xml" rel="self"/>
  <link href="https://daal.cloud/articles/"/>
  <id>https://daal.cloud/articles/</id>
  
  <updated>2026-10-11T00:00:00.000Z</updated>
  
  <author>
    <name>Vince van Daal</name>
    <uri>https://vandaal.io</uri>
  </author>
  
  <entry>
    <title>AirPods 5 vs AirPods Pro 3: sealed or open?</title>
    <link href="https://daal.cloud/articles/airpods-5-vs-pro-3/"/>
    <id>https://daal.cloud/articles/airpods-5-vs-pro-3/</id>
    <published>2026-10-11T00:00:00.000Z</published>
    <updated>2026-10-11T00:00:00.000Z</updated>
    <summary>AirPods 5 bring noise cancelling to Apple&#39;s cheapest buds. In lab tests Pro 3 still cut far more noise, last longer and add health features. The real question is whether you can live with silicone tips in your ears.</summary>
    <content type="html">&lt;div class=&quot;ap-hero&quot;&gt;
&lt;div class=&quot;ap-win ap-win--open&quot;&gt;&lt;div class=&quot;ap-win-title&quot;&gt;airpods5.exe&lt;/div&gt;&lt;div class=&quot;ap-win-body&quot;&gt;&lt;span class=&quot;ap-hero-name&quot;&gt;AirPods 5&lt;/span&gt;&lt;span class=&quot;ap-price&quot;&gt;$129&lt;/span&gt;&lt;span class=&quot;ap-price-alt&quot;&gt;$149 with wireless case&lt;/span&gt;&lt;ul class=&quot;ap-hero-facts&quot;&gt;&lt;li&gt;Open fit, no tips&lt;/li&gt;&lt;li&gt;59% less noise (lab)&lt;/li&gt;&lt;li&gt;5.1 h per charge (lab)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;ap-vs&quot; aria-hidden=&quot;true&quot;&gt;vs&lt;/div&gt;
&lt;div class=&quot;ap-win ap-win--sealed&quot;&gt;&lt;div class=&quot;ap-win-title&quot;&gt;pro3.exe&lt;/div&gt;&lt;div class=&quot;ap-win-body&quot;&gt;&lt;span class=&quot;ap-hero-name&quot;&gt;AirPods Pro 3&lt;/span&gt;&lt;span class=&quot;ap-price&quot;&gt;$249&lt;/span&gt;&lt;span class=&quot;ap-price-alt&quot;&gt;often discounted&lt;/span&gt;&lt;ul class=&quot;ap-hero-facts&quot;&gt;&lt;li&gt;In-ear seal, 5 tip sizes&lt;/li&gt;&lt;li&gt;90% less noise (lab)&lt;/li&gt;&lt;li&gt;8.7 h per charge (lab)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Apple finally put noise cancelling in its cheapest earbuds, which makes this a harder call than it used to be. In SoundGuys&#39; lab, Pro 3 cut about 90% of the perceived noise around you against 59% for AirPods 5. They also run longer per charge and carry health features AirPods 5 don&#39;t have. None of that matters if you can&#39;t stand silicone tips in your ears, and plenty of people can&#39;t. No spec sheet will tell you which camp you&#39;re in, so start there.&lt;/p&gt;
&lt;div class=&quot;ap-tldr&quot; role=&quot;note&quot; aria-label=&quot;Short version&quot;&gt;
&lt;div class=&quot;ap-tldr-col ap-tldr-col--open&quot;&gt;&lt;p class=&quot;ap-tldr-head&quot;&gt;Get AirPods 5 if&lt;/p&gt;&lt;ul&gt;&lt;li&gt;ear tips make you want to rip them out&lt;/li&gt;&lt;li&gt;your noise is office hum, not jet engines&lt;/li&gt;&lt;li&gt;buds go in and out of the case all day&lt;/li&gt;&lt;li&gt;$100 is $100&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;div class=&quot;ap-tldr-col ap-tldr-col--sealed&quot;&gt;&lt;p class=&quot;ap-tldr-head&quot;&gt;Get Pro 3 if&lt;/p&gt;&lt;ul&gt;&lt;li&gt;you fly or commute somewhere loud every week&lt;/li&gt;&lt;li&gt;you want hearing-health or heart-rate features&lt;/li&gt;&lt;li&gt;you listen for hours without a break&lt;/li&gt;&lt;li&gt;you run or train in them&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;ap-legend-key&quot;&gt;Every number on this page is tagged: &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;lab&lt;/span&gt; measured by an independent lab, &lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;estimate&lt;/span&gt; worked out here (method in &lt;a href=&quot;#where-the-numbers-come-from&quot;&gt;the data section&lt;/a&gt;), &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt; Apple&#39;s own rating.&lt;/p&gt;
&lt;nav class=&quot;ap-jump&quot; aria-label=&quot;Jump to section&quot;&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;#how-much-noise-gets-through&quot;&gt;Noise&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#comfort-comes-down-to-your-ears&quot;&gt;Comfort&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#what-airpods-5-give-up&quot;&gt;Missing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#battery&quot;&gt;Battery&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#side-by-side&quot;&gt;Specs&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#which-one-is-yours&quot;&gt;Decide&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#looking-ahead&quot;&gt;Ahead&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#where-the-numbers-come-from&quot;&gt;Data&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/nav&gt;
&lt;h2 id=&quot;how-much-noise-gets-through&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#how-much-noise-gets-through&quot;&gt;#&lt;/a&gt; How much noise gets through&lt;/h2&gt;
&lt;p&gt;AirPods 5 cancel noise in software. Pro 3 do the same job harder and add a physical seal on top. Start with what the labs measured.&lt;/p&gt;
&lt;div class=&quot;ap-lab&quot;&gt;
&lt;div class=&quot;ap-labtile ap-labtile--open&quot;&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;lab&lt;/span&gt;&lt;b&gt;59%&lt;/b&gt;&lt;span&gt;less perceived noise, AirPods 5, ANC on. SoundGuys.&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-labtile ap-labtile--sealed&quot;&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;lab&lt;/span&gt;&lt;b&gt;90%&lt;/b&gt;&lt;span&gt;less perceived noise, Pro 3, ANC on. SoundGuys.&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-labtile ap-labtile--sealed&quot;&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;lab&lt;/span&gt;&lt;b&gt;−28.7 dB&lt;/b&gt;&lt;span&gt;overall noise isolation, Pro 3, ANC on. RTINGS.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Put another way: of the noise that would normally reach you, about 41% still gets through AirPods 5 and about 10% gets through Pro 3. Apple&#39;s marketing says &amp;quot;up to 3x more ANC&amp;quot;. The lab gap is closer to 4x, though CNN found it smaller in day-to-day use.&lt;/p&gt;
&lt;div class=&quot;ap-win ap-bandwin&quot;&gt;
&lt;div class=&quot;ap-win-title&quot;&gt;isolation-by-band.csv&lt;/div&gt;
&lt;div class=&quot;ap-win-body&quot;&gt;
&lt;p class=&quot;ap-bands-sub&quot;&gt;Noise reduction with ANC on, in dB. Longer bar = quieter.&lt;/p&gt;
&lt;div class=&quot;ap-bands&quot;&gt;
&lt;p class=&quot;ap-bands-group&quot;&gt;Bass &lt;span&gt;engine rumble, traffic&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;ap-band ap-band--open ap-band--est&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;AirPods 5&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:17.1%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−6 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;estimate&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-band ap-band--sealed&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;Pro 3&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:68%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−23.8 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/div&gt;
&lt;p class=&quot;ap-bands-group&quot;&gt;Mids &lt;span&gt;voices, keyboards&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;ap-band ap-band--open ap-band--est&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;AirPods 5&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:45.7%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−16 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;estimate&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-band ap-band--sealed&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;Pro 3&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:92.6%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−32.4 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/div&gt;
&lt;p class=&quot;ap-bands-group&quot;&gt;Treble &lt;span&gt;AC whine, dish clatter&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;ap-band ap-band--open ap-band--est&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;AirPods 5&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:51.4%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−18 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;estimate&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-band ap-band--sealed&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;Pro 3&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:87.1%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−30.5 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/div&gt;
&lt;p class=&quot;ap-bands-group&quot;&gt;Overall&lt;/p&gt;
&lt;div class=&quot;ap-band ap-band--open ap-band--est&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;AirPods 5&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:36.9%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;≈ −12.9 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;from lab %&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-band ap-band--sealed&quot;&gt;&lt;span class=&quot;ap-band-name&quot;&gt;Pro 3&lt;/span&gt;&lt;span class=&quot;ap-band-track&quot;&gt;&lt;span class=&quot;ap-band-fill&quot; style=&quot;--w:82%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;−28.7 dB&lt;/b&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;ap-fine&quot;&gt;Scale 0 to 35 dB. RTINGS has measured AirPods 5 too, but those values sit behind its paid membership, so the AirPods 5 bands are estimated from what the labs have published. See &lt;a href=&quot;#where-the-numbers-come-from&quot;&gt;how&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now feel it. Pick a place, flip between the two, then switch ANC off to see what the seal does on its own. The meter shows how much of the perceived loudness still reaches your ear.&lt;/p&gt;
&lt;div class=&quot;ap-win ap-room ap-needs-js&quot; id=&quot;ap-room&quot;&gt;
&lt;div class=&quot;ap-win-title&quot;&gt;noise-room.exe&lt;/div&gt;
&lt;div class=&quot;ap-win-body&quot;&gt;
&lt;div class=&quot;ap-seg&quot; role=&quot;group&quot; aria-label=&quot;Choose earbuds&quot;&gt;&lt;button type=&quot;button&quot; data-v=&quot;ap5&quot; aria-pressed=&quot;true&quot;&gt;AirPods 5&lt;/button&gt;&lt;button type=&quot;button&quot; data-v=&quot;pro&quot; aria-pressed=&quot;false&quot;&gt;AirPods Pro 3&lt;/button&gt;&lt;/div&gt;
&lt;div class=&quot;ap-chips&quot; role=&quot;group&quot; aria-label=&quot;Choose a place&quot;&gt;&lt;button type=&quot;button&quot; data-s=&quot;desk&quot; aria-pressed=&quot;true&quot;&gt;Office hum&lt;/button&gt;&lt;button type=&quot;button&quot; data-s=&quot;train&quot; aria-pressed=&quot;false&quot;&gt;Train &amp;amp; traffic&lt;/button&gt;&lt;button type=&quot;button&quot; data-s=&quot;plane&quot; aria-pressed=&quot;false&quot;&gt;Plane cabin&lt;/button&gt;&lt;button type=&quot;button&quot; data-s=&quot;cafe&quot; aria-pressed=&quot;false&quot;&gt;Cafe voices&lt;/button&gt;&lt;/div&gt;
&lt;div class=&quot;ap-crt&quot;&gt;&lt;canvas aria-hidden=&quot;true&quot;&gt;&lt;/canvas&gt;&lt;span class=&quot;ap-crt-tag&quot; aria-hidden=&quot;true&quot;&gt;ANC ON&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-legend&quot;&gt;&lt;span&gt;&lt;i class=&quot;ap-key ap-key--noise&quot;&gt;&lt;/i&gt;Noise around you&lt;/span&gt;&lt;span&gt;&lt;i class=&quot;ap-key ap-key--out&quot;&gt;&lt;/i&gt;What reaches your ear&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-meter&quot;&gt;&lt;span class=&quot;ap-meter-label&quot;&gt;Gets through&lt;/span&gt;&lt;div class=&quot;ap-leds&quot; aria-hidden=&quot;true&quot;&gt;&lt;/div&gt;&lt;output class=&quot;ap-meter-out&quot; aria-live=&quot;polite&quot;&gt;&lt;/output&gt;&lt;/div&gt;
&lt;div class=&quot;ap-readout&quot;&gt;&lt;span class=&quot;ap-badge&quot; data-kind=&quot;est&quot;&gt;estimate&lt;/span&gt;&lt;span class=&quot;ap-readout-text&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p class=&quot;ap-room-note&quot;&gt;&lt;/p&gt;
&lt;label class=&quot;ap-toggle&quot;&gt;&lt;input type=&quot;checkbox&quot; checked&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;span&gt;Noise cancelling on&lt;/span&gt;&lt;/label&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;details class=&quot;ap-more&quot;&gt;
&lt;summary&gt;Every value the noise room uses&lt;/summary&gt;
&lt;p&gt;Share of perceived loudness that reaches your ear. All four columns are estimates built from the band values above; the ANC-off columns are the roughest. Method in &lt;a href=&quot;#where-the-numbers-come-from&quot;&gt;the data section&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Place (bass / mid / treble mix)&lt;/th&gt;
&lt;th&gt;AirPods 5, ANC on&lt;/th&gt;
&lt;th&gt;Pro 3, ANC on&lt;/th&gt;
&lt;th&gt;AirPods 5, ANC off&lt;/th&gt;
&lt;th&gt;Pro 3, ANC off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Office hum (30 / 40 / 30)&lt;/td&gt;
&lt;td&gt;42%&lt;/td&gt;
&lt;td&gt;14%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;54%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Train &amp;amp; traffic (60 / 30 / 10)&lt;/td&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plane cabin (50 / 35 / 15)&lt;/td&gt;
&lt;td&gt;49%&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cafe voices (10 / 70 / 20)&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;49%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;p&gt;The pattern is consistent across both labs: AirPods 5 are decent in the mids and treble and weak in the low bass. That&#39;s why reviewers still heard engine drone on long-haul flights, and why a train&#39;s rumble survives. Pro 3 are strong everywhere, and strongest exactly where AirPods 5 are weakest. Pro 3 users wear theirs as earplugs.&lt;/p&gt;
&lt;p&gt;One honest caveat on voices. Lab tests use steady noise, and on that AirPods 5 handle mid-range chatter well; RTINGS calls it &amp;quot;handily blocked out&amp;quot;. Real conversations come and go, which is harder for any ANC to track, so expect a busy cafe to sound a bit worse than the table says.&lt;/p&gt;
&lt;h2 id=&quot;comfort-comes-down-to-your-ears&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#comfort-comes-down-to-your-ears&quot;&gt;#&lt;/a&gt; Comfort comes down to your ears&lt;/h2&gt;
&lt;p&gt;There&#39;s no objectively comfier pair. One rests in your outer ear, the other plugs the canal. Most people know within a minute which camp they&#39;re in.&lt;/p&gt;
&lt;div class=&quot;ap-fit&quot;&gt;
&lt;div class=&quot;ap-card ap-card--open&quot;&gt;
&lt;svg class=&quot;ap-fig&quot; viewBox=&quot;0 0 300 130&quot; role=&quot;img&quot; aria-label=&quot;Open-fit bud resting at the entrance of the ear canal, with air flowing around it&quot;&gt;&lt;path class=&quot;ap-fig-ear&quot; d=&quot;M10 30 Q120 20 170 52 L300 58 L300 78 L170 82 Q120 112 10 104&quot;/&gt;&lt;ellipse class=&quot;ap-fig-bud&quot; cx=&quot;96&quot; cy=&quot;66&quot; rx=&quot;40&quot; ry=&quot;30&quot;/&gt;&lt;rect class=&quot;ap-fig-bud&quot; x=&quot;60&quot; y=&quot;78&quot; width=&quot;16&quot; height=&quot;50&quot; rx=&quot;8&quot;/&gt;&lt;path class=&quot;ap-fig-air&quot; d=&quot;M140 44 q14 -4 28 6 M140 90 q14 4 28 -6&quot;/&gt;&lt;path class=&quot;ap-fig-air&quot; d=&quot;M190 68 h80&quot;/&gt;&lt;/svg&gt;
&lt;h3 id=&quot;airpods-5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#airpods-5&quot;&gt;#&lt;/a&gt; AirPods 5&lt;/h3&gt;
&lt;p&gt;Open fit, one size, no tips.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No plugged-up feeling or pressure from a seal. Easy to wear for hours.&lt;/li&gt;
&lt;li&gt;You still hear traffic on a run or a walk.&lt;/li&gt;
&lt;li&gt;Light: 4.3 g per bud, and a pocket-friendly case.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not so good&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One shape. It fits your ears or it doesn&#39;t, and there&#39;s nothing to adjust. SoundGuys found their noise results swing with how the bud sits.&lt;/li&gt;
&lt;li&gt;Less secure for sprints or fast head movement.&lt;/li&gt;
&lt;li&gt;Some reviewers feel a faint pressure from the ANC at first.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;ap-card ap-card--sealed&quot;&gt;
&lt;svg class=&quot;ap-fig&quot; viewBox=&quot;0 0 300 130&quot; role=&quot;img&quot; aria-label=&quot;Pro bud with a silicone tip sealing the ear canal&quot;&gt;&lt;path class=&quot;ap-fig-ear&quot; d=&quot;M10 30 Q120 20 170 52 L300 58 L300 78 L170 82 Q120 112 10 104&quot;/&gt;&lt;ellipse class=&quot;ap-fig-bud&quot; cx=&quot;92&quot; cy=&quot;66&quot; rx=&quot;36&quot; ry=&quot;27&quot;/&gt;&lt;rect class=&quot;ap-fig-bud&quot; x=&quot;58&quot; y=&quot;76&quot; width=&quot;15&quot; height=&quot;50&quot; rx=&quot;7.5&quot;/&gt;&lt;path class=&quot;ap-fig-tip&quot; d=&quot;M126 56 Q160 50 186 56 L186 80 Q160 86 126 80 Z&quot;/&gt;&lt;line class=&quot;ap-fig-wall&quot; x1=&quot;196&quot; y1=&quot;54&quot; x2=&quot;196&quot; y2=&quot;82&quot;/&gt;&lt;/svg&gt;
&lt;h3 id=&quot;airpods-pro-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#airpods-pro-3&quot;&gt;#&lt;/a&gt; AirPods Pro 3&lt;/h3&gt;
&lt;p&gt;In-ear, foam-infused tips in five sizes (XXS to L).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tip sizes let you tune the fit, very small ears included.&lt;/li&gt;
&lt;li&gt;Locked in for workouts and running.&lt;/li&gt;
&lt;li&gt;The seal blocks noise even with ANC off.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not so good&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The plugged, sealed feeling never sits right with some people.&lt;/li&gt;
&lt;li&gt;Some owners get sore on long sessions. Dropping a tip size often fixes it.&lt;/li&gt;
&lt;li&gt;RTINGS notes worn tips may block less treble over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ap-stats&quot;&gt;
&lt;div class=&quot;ap-stat ap-stat--open&quot;&gt;&lt;b&gt;50.1 mm&lt;/b&gt;&lt;span&gt;AirPods 5 case, long side. 34.7 g.&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;ap-stat ap-stat--sealed&quot;&gt;&lt;b&gt;62.2 mm&lt;/b&gt;&lt;span&gt;Pro 3 case, long side, with lanyard loop.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;what-airpods-5-give-up&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#what-airpods-5-give-up&quot;&gt;#&lt;/a&gt; What AirPods 5 give up&lt;/h2&gt;
&lt;p&gt;Tap a row for why it matters. The $129 model loses a few extra things, so pick which one you&#39;re weighing.&lt;/p&gt;
&lt;div class=&quot;ap-seg ap-seg--filter ap-needs-js&quot; role=&quot;group&quot; aria-label=&quot;Which AirPods 5&quot;&gt;&lt;button type=&quot;button&quot; data-v=&quot;base&quot; aria-pressed=&quot;true&quot;&gt;AirPods 5 ($129)&lt;/button&gt;&lt;button type=&quot;button&quot; data-v=&quot;wcc&quot; aria-pressed=&quot;false&quot;&gt;Wireless case ($149)&lt;/button&gt;&lt;/div&gt;
&lt;div class=&quot;ap-lack&quot; id=&quot;ap-lack&quot;&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;A sealed fit and much stronger ANC&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;The biggest gap. In SoundGuys&#39; lab, AirPods 5 cut 59% of perceived noise and Pro 3 cut 90%. The difference lives in the bass: on a long-haul flight a reviewer still heard engine drone through AirPods 5. Pro 3 can double as earplugs. AirPods 5 can&#39;t.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Hearing health features&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Hearing Test, the clinical-grade Hearing Aid mode, Hearing Protection, Conversation Boost and Loud Sound Reduction are Pro-only. If your hearing is slipping, this alone justifies Pro 3.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Heart-rate sensing in workouts&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Pro 3 track heart rate on their own, no Apple Watch needed. AirPods 5 have no heart-rate sensor.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Battery life per charge&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Lab-measured with ANC on: 8 h 42 min for Pro 3 (SoundGuys) against 5.1 h for AirPods 5 with the wireless case (RTINGS). That&#39;s the difference between a whole transatlantic leg and a mid-flight top-up.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Sound detail and Spatial Audio depth&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Reviewers found AirPods 5 muddier in the lower mids, with flatter voices on podcasts and a shallower Spatial Audio stage. RTINGS measured their bass and treble about 5 dB under its target, against +1 dB bass and −3 dB treble for Pro 3. They also play quieter, so you turn them up and leak more sound.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Precision Finding for the case&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;The Pro 3 case has an Ultra Wideband chip that points you to it with arrows on a compatible iPhone.&lt;/p&gt;&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;MagSafe charging and a lanyard loop&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Minor, but only the Pro 3 case snaps onto MagSafe and takes a strap.&lt;/p&gt;&lt;/details&gt;
&lt;details data-base&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Volume swipe on the stem&lt;em&gt;$129 model only&lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;The $149 model swipes for volume. The $129 model doesn&#39;t, so you reach for your phone or Siri instead.&lt;/p&gt;&lt;/details&gt;
&lt;details data-base&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Wireless charging&lt;em&gt;$129 model only&lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;The $129 case is USB-C only. The $149 case also takes Qi and Apple Watch chargers.&lt;/p&gt;&lt;/details&gt;
&lt;details data-base&gt;&lt;summary&gt;&lt;span class=&quot;ap-x&quot; aria-hidden=&quot;true&quot;&gt;✕&lt;/span&gt;&lt;span&gt;Find My speaker in the case&lt;em&gt;$129 model only&lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;ap-chev&quot; aria-hidden=&quot;true&quot;&gt;›&lt;/span&gt;&lt;/summary&gt;&lt;p&gt;Without it, a case lost under the sofa stays silent.&lt;/p&gt;&lt;/details&gt;
&lt;/div&gt;
&lt;div class=&quot;ap-keep&quot;&gt;
&lt;p class=&quot;ap-keep-head&quot;&gt;Every AirPods 5 keeps&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Adaptive Audio&lt;/li&gt;&lt;li&gt;Transparency&lt;/li&gt;&lt;li&gt;Conversation Awareness&lt;/li&gt;&lt;li&gt;Live Translation&lt;/li&gt;&lt;li&gt;Hands-free Siri&lt;/li&gt;&lt;li&gt;Head gestures&lt;/li&gt;&lt;li&gt;Personalized Spatial Audio&lt;/li&gt;&lt;li&gt;IP57 dust &amp;amp; water&lt;/li&gt;&lt;li&gt;iOS 27 custom EQ&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id=&quot;battery&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#battery&quot;&gt;#&lt;/a&gt; Battery&lt;/h2&gt;
&lt;p&gt;Apple&#39;s ratings next to what the labs got, all with noise cancelling on. Faded bars are Apple&#39;s numbers, solid bars are lab runs.&lt;/p&gt;
&lt;div class=&quot;ap-bars&quot; id=&quot;ap-bars&quot;&gt;
&lt;p class=&quot;ap-bars-head&quot;&gt;Listening on one charge&lt;/p&gt;
&lt;div class=&quot;ap-bar ap-bar--open ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:40%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;4 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--open ap-bar--none&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;lab&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-empty&quot;&gt;not lab-tested yet&lt;/span&gt;&lt;/span&gt;&lt;b&gt;?&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--open ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 wireless &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:50%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;5 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--open&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 wireless &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:51%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;5.1 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--sealed ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;Pro 3 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:80%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;8 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--sealed&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;Pro 3 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;soundguys&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:87%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;8.7 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--sealed&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;Pro 3 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:90%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;~9 h&lt;/b&gt;&lt;/div&gt;
&lt;p class=&quot;ap-bars-head&quot;&gt;Total with the case&lt;/p&gt;
&lt;div class=&quot;ap-bar ap-bar--open ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:80%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;20 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--open ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 wireless &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:88%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;22 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--open&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;AirPods 5 wireless &lt;span class=&quot;ap-badge&quot; data-kind=&quot;lab&quot;&gt;rtings&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:90%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;22.5 h&lt;/b&gt;&lt;/div&gt;
&lt;div class=&quot;ap-bar ap-bar--sealed ap-bar--rated&quot;&gt;&lt;span class=&quot;ap-bar-name&quot;&gt;Pro 3 &lt;span class=&quot;ap-badge&quot; data-kind=&quot;apple&quot;&gt;apple&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ap-bar-track&quot;&gt;&lt;span class=&quot;ap-bar-fill&quot; style=&quot;--w:96%&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;24 h&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Apple&#39;s ratings hold up: the labs got the same or slightly more. Nobody has published a lab run for the $129 model yet. Hands-on, T3 had 12% left after about four and a half hours with ANC on, and CNN&#39;s pair gave out between four and five hours into a flight.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If your buds go back in the case between calls, AirPods 5 battery is a non-issue. If you listen nonstop, Pro 3 last about 70% longer per session than the wireless-case AirPods 5 in lab runs (8.7 h against 5.1 h).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;side-by-side&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#side-by-side&quot;&gt;#&lt;/a&gt; Side by side&lt;/h2&gt;
&lt;div class=&quot;ap-specs&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AirPods 5&lt;/th&gt;
&lt;th&gt;AirPods 5 wireless&lt;/th&gt;
&lt;th&gt;Pro 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price (US)&lt;/td&gt;
&lt;td&gt;$129&lt;/td&gt;
&lt;td&gt;$149&lt;/td&gt;
&lt;td&gt;$249&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fit&lt;/td&gt;
&lt;td&gt;Open, no tips&lt;/td&gt;
&lt;td&gt;Open, no tips&lt;/td&gt;
&lt;td&gt;In-ear, 5 tip sizes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noise cut, lab (SoundGuys)&lt;/td&gt;
&lt;td&gt;59%&lt;/td&gt;
&lt;td&gt;59%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noise isolation, lab (RTINGS)&lt;/td&gt;
&lt;td&gt;Members only&lt;/td&gt;
&lt;td&gt;Members only&lt;/td&gt;
&lt;td&gt;−28.7 dB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery, Apple (bud / total)&lt;/td&gt;
&lt;td&gt;4 h / 20 h&lt;/td&gt;
&lt;td&gt;5 h / 22 h&lt;/td&gt;
&lt;td&gt;8 h / 24 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery, lab&lt;/td&gt;
&lt;td&gt;Not tested yet&lt;/td&gt;
&lt;td&gt;5.1 h / 22.5 h&lt;/td&gt;
&lt;td&gt;8.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stem controls&lt;/td&gt;
&lt;td&gt;Press&lt;/td&gt;
&lt;td&gt;Press + volume swipe&lt;/td&gt;
&lt;td&gt;Touch + volume swipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case charging&lt;/td&gt;
&lt;td&gt;USB-C&lt;/td&gt;
&lt;td&gt;USB-C, Qi, Watch&lt;/td&gt;
&lt;td&gt;+ MagSafe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find My speaker&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes + Precision Finding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hearing health&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heart-rate sensor&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dust &amp;amp; water&lt;/td&gt;
&lt;td&gt;IP57&lt;/td&gt;
&lt;td&gt;IP57&lt;/td&gt;
&lt;td&gt;IP57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Released&lt;/td&gt;
&lt;td&gt;Sep 2026&lt;/td&gt;
&lt;td&gt;Sep 2026&lt;/td&gt;
&lt;td&gt;Sep 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Both AirPods 5 models use the same buds, so one noise result covers both. Eurozone list price for the base AirPods 5 is €149. Retailers discount Pro 3 regularly, which can shrink the gap a lot.&lt;/p&gt;
&lt;h2 id=&quot;which-one-is-yours&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#which-one-is-yours&quot;&gt;#&lt;/a&gt; Which one is yours?&lt;/h2&gt;
&lt;p&gt;Flip on whatever applies to you.&lt;/p&gt;
&lt;div class=&quot;ap-quiz ap-needs-js&quot;&gt;
&lt;label class=&quot;ap-q&quot;&gt;&lt;span&gt;I fly or commute somewhere loud every week&lt;/span&gt;&lt;input type=&quot;checkbox&quot; data-k=&quot;loud&quot;&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/label&gt;
&lt;label class=&quot;ap-q&quot;&gt;&lt;span&gt;Silicone ear tips bother me&lt;/span&gt;&lt;input type=&quot;checkbox&quot; data-k=&quot;tips&quot;&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/label&gt;
&lt;label class=&quot;ap-q&quot;&gt;&lt;span&gt;I want hearing-health or heart-rate features&lt;/span&gt;&lt;input type=&quot;checkbox&quot; data-k=&quot;health&quot;&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/label&gt;
&lt;label class=&quot;ap-q&quot;&gt;&lt;span&gt;I listen 5+ hours without a break&lt;/span&gt;&lt;input type=&quot;checkbox&quot; data-k=&quot;long&quot;&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/label&gt;
&lt;label class=&quot;ap-q&quot;&gt;&lt;span&gt;Saving $100 matters more than the best ANC&lt;/span&gt;&lt;input type=&quot;checkbox&quot; data-k=&quot;budget&quot;&gt;&lt;span class=&quot;ap-toggle-ui&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/label&gt;
&lt;/div&gt;
&lt;div class=&quot;ap-win ap-result&quot; id=&quot;ap-result&quot; aria-live=&quot;polite&quot;&gt;
&lt;div class=&quot;ap-win-title&quot;&gt;verdict.txt&lt;/div&gt;
&lt;div class=&quot;ap-win-body&quot;&gt;&lt;b class=&quot;ap-result-title&quot;&gt;Pick what fits&lt;/b&gt;&lt;p class=&quot;ap-result-text&quot;&gt;Short version: if tips bother you or the $100 matters, AirPods 5 with the wireless case. Loud commutes, health features or long sessions point to Pro 3.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;looking-ahead&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#looking-ahead&quot;&gt;#&lt;/a&gt; Looking ahead&lt;/h2&gt;
&lt;div class=&quot;ap-ahead&quot;&gt;
&lt;h3 id=&quot;pro-3-won-t-go-stale-soon&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#pro-3-won-t-go-stale-soon&quot;&gt;#&lt;/a&gt; Pro 3 won&#39;t go stale soon&lt;/h3&gt;
&lt;p&gt;Pro 2 lasted three years before Pro 3 replaced it. That&#39;s a pattern, not a promise, but buying Pro 3 a year in isn&#39;t buying at the end of its run.&lt;/p&gt;
&lt;h3 id=&quot;software-is-closing-the-gap-hardware-isn-t&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#software-is-closing-the-gap-hardware-isn-t&quot;&gt;#&lt;/a&gt; Software is closing the gap, hardware isn&#39;t&lt;/h3&gt;
&lt;p&gt;Live Translation, the new Siri, head gestures and iOS 27&#39;s custom EQ land on both. What keeps Pro ahead is physical: the seal, the sensors and the bigger battery. The bass gap in the band chart is a seal problem, and no update fixes that.&lt;/p&gt;
&lt;h3 id=&quot;watch-the-price-not-the-calendar&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#watch-the-price-not-the-calendar&quot;&gt;#&lt;/a&gt; Watch the price, not the calendar&lt;/h3&gt;
&lt;p&gt;Black Friday is a few weeks out. Pro 3 discounts can cut the $100 gap meaningfully, so if you lean Pro, wait for a deal.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id=&quot;where-the-numbers-come-from&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#where-the-numbers-come-from&quot;&gt;#&lt;/a&gt; Where the numbers come from&lt;/h2&gt;
&lt;p&gt;Three kinds of number appear on this page, and each one is tagged. Here&#39;s where every one of them comes from, so you can check them or throw them out.&lt;/p&gt;
&lt;h3 id=&quot;measured-by-a-lab&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#measured-by-a-lab&quot;&gt;#&lt;/a&gt; Measured by a lab&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Lab&lt;/th&gt;
&lt;th&gt;How it was measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;59% less perceived noise, ANC on&lt;/td&gt;
&lt;td&gt;AirPods 5&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.soundguys.com/apple-airpods-5-review-163483/&quot;&gt;SoundGuys&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Test head (B&amp;amp;K 5128) recorded with ANC on, ANC off and no earbuds, then averaged as a cut in perceived loudness. SoundGuys warns open-fit results vary with how the bud sits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90% less perceived noise, ANC on&lt;/td&gt;
&lt;td&gt;Pro 3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.soundguys.com/airpods-3-pro-vs-airpods-4-with-anc-which-apple-earbuds-should-you-buy-144911/&quot;&gt;SoundGuys&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Same method.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;−28.7 dB overall; bass −23.8, mid −32.4, treble −30.5 dB&lt;/td&gt;
&lt;td&gt;Pro 3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-pro-3&quot;&gt;RTINGS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;RTINGS&#39; noise isolation test, ANC on. Taken from RTINGS&#39; public comparison tables; RTINGS now shows these values to paying members only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bass and treble about −5 dB vs target&lt;/td&gt;
&lt;td&gt;AirPods 5&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-5&quot;&gt;RTINGS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Frequency response against RTINGS&#39; target curve. Pro 3: +1 dB bass, −3 dB treble.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.1 h continuous, 22.5 h total, ANC on&lt;/td&gt;
&lt;td&gt;AirPods 5 wireless&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-5&quot;&gt;RTINGS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Continuous playback until the buds die, then recharged from the case.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 h 42 min, ANC on&lt;/td&gt;
&lt;td&gt;Pro 3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.soundguys.com/airpods-3-pro-vs-airpods-4-with-anc-which-apple-earbuds-should-you-buy-144911/&quot;&gt;SoundGuys&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Music peaking at 75 dB SPL until the first bud stops (&lt;a href=&quot;https://www.soundguys.com/how-we-test/&quot;&gt;method&lt;/a&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 9 h, ANC on&lt;/td&gt;
&lt;td&gt;Pro 3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-pro-3&quot;&gt;RTINGS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Continuous playback, ANC on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 20 dB in the mids and treble&lt;/td&gt;
&lt;td&gt;AirPods 4 with ANC&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.soundguys.com/airpods-3-pro-vs-airpods-4-with-anc-which-apple-earbuds-should-you-buy-144911/&quot;&gt;SoundGuys&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not on the chart; used as a reference point for the estimates below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;estimated-here&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#estimated-here&quot;&gt;#&lt;/a&gt; Estimated here&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AirPods 5 per band: bass −6, mid −16, treble −18 dB.&lt;/strong&gt; No public per-band measurement exists yet. The shape comes from what both labs say about them: SoundGuys found they work best between about 1 and 9 kHz and struggle below 500 Hz, and RTINGS writes that they &amp;quot;struggle to block out sounds like engine rumble&amp;quot; while whine and chatter are &amp;quot;handily blocked out&amp;quot;. AirPods 4 with ANC measured about 20 dB in the mids and treble, which sets a sensible ceiling for an open-fit design. I then checked the three values against the lab: they average out to a 57% cut in perceived loudness, next to SoundGuys&#39; measured 59%.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AirPods 5 overall: about −12.9 dB.&lt;/strong&gt; Converted straight from SoundGuys&#39; 59% with the rule below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ANC off: AirPods 5 0 / −1 / −4 dB, Pro 3 −2 / −10 / −20 dB&lt;/strong&gt; (bass / mid / treble). These are the shakiest numbers on the page. Neither lab publishes ANC-off results for these models. An open shell doesn&#39;t seal, so AirPods 5 block next to nothing on their own. The Pro 3 figures are typical passive isolation for silicone-tipped earbuds: little bass, a fair bit of treble.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Per-place results.&lt;/strong&gt; Each place in the noise room is a mix of bass, mids and treble: office hum 30/40/30, train and traffic 60/30/10, plane cabin 50/35/15, cafe voices 10/70/20. Those mixes are my judgement of where each noise sits, not measurements.&lt;/p&gt;
&lt;h3 id=&quot;the-maths&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-maths&quot;&gt;#&lt;/a&gt; The maths&lt;/h3&gt;
&lt;p&gt;Rule of thumb from psychoacoustics: every 10 dB of reduction roughly halves how loud something sounds. So the share of loudness that gets through is &lt;code&gt;2^(−dB / 10)&lt;/code&gt;. For each place:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gets_through = Σ mix[band] × 2^(−reduction[band] / 10)

Pro 3, train, ANC on:
  0.60 × 2^(−23.8/10) + 0.30 × 2^(−32.4/10) + 0.10 × 2^(−30.5/10)
= 0.60 × 0.192 + 0.30 × 0.106 + 0.10 × 0.121
= 0.16  →  16% gets through
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sanity check: run RTINGS&#39; three Pro 3 bands through the same formula with equal weights and you get an 86% cut. SoundGuys measured 90% on a different rig. Two labs, two methods, one story. The noise room&#39;s moving wave is decoration; the meter and the percentages come from this formula.&lt;/p&gt;
&lt;h2 id=&quot;sources&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#sources&quot;&gt;#&lt;/a&gt; Sources&lt;/h2&gt;
&lt;p&gt;Lab data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.soundguys.com/apple-airpods-5-review-163483/&quot;&gt;SoundGuys: AirPods 5 review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.soundguys.com/airpods-3-pro-vs-airpods-4-with-anc-which-apple-earbuds-should-you-buy-144911/&quot;&gt;SoundGuys: AirPods Pro 3 vs AirPods 4 with ANC (Pro 3 and AirPods 4 lab results)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.soundguys.com/how-we-test/&quot;&gt;SoundGuys: how we test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-5&quot;&gt;RTINGS: AirPods 5 review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rtings.com/headphones/reviews/apple/airpods-pro-3&quot;&gt;RTINGS: AirPods Pro 3 review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rtings.com/headphones/tools/compare/apple-airpods-5-apple-airpods-pro-3/154358/113044&quot;&gt;RTINGS: AirPods 5 vs AirPods Pro 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Specs, prices and hands-on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.apple.com/li/newsroom/2026/09/apple-introduces-airpods-5/&quot;&gt;Apple Newsroom: AirPods 5 announcement (Sep 9, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.macrumors.com/guide/airpods-5-vs-airpods-pro-3/&quot;&gt;MacRumors: AirPods 5 vs AirPods Pro 3 buyer&#39;s guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnn.com/cnn-underscored/reviews/airpods-pro-3-vs-airpods-5&quot;&gt;CNN Underscored: AirPods Pro 3 vs AirPods 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bgr.com/2269100/airpods-5-review/&quot;&gt;BGR: AirPods 5 review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.t3.com/tech/earbuds/apple-airpods-5-review&quot;&gt;T3: AirPods 5 review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.iclarified.com/102282/airpods-5-vs-airpods-pro-3-which-should-you-buy&quot;&gt;iClarified: AirPods 5 vs Pro 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.today.com/shop/apple-airpods-5-review-rcna599286&quot;&gt;TODAY: AirPods 5 hands-on&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prices are US list unless noted. Lab figures checked October 11, 2026.&lt;/p&gt;
</content>
    <category term="apple"/><category term="airpods"/><category term="audio"/><category term="hardware"/><category term="buying-guide"/>
  </entry>
  
  <entry>
    <title>Co-writing with AI, without the slop</title>
    <link href="https://daal.cloud/articles/co-writing-with-ai/"/>
    <id>https://daal.cloud/articles/co-writing-with-ai/</id>
    <published>2026-05-13T00:00:00.000Z</published>
    <updated>2026-05-13T00:00:00.000Z</updated>
    <summary>How I draft the articles on this site with Claude as a co-writer. Markers, /tagore, and a human pass at the end. The &quot;ghostwriter&quot; framing doesn&#39;t quite fit. &quot;Co-writer&quot; does.</summary>
    <content type="html">&lt;p&gt;I use AI to help write the articles on this site. Shocking, I know. It&#39;s 2026. My fridge probably uses AI to schedule its own defrost cycle, the toaster has a personality, and at this point if an article wasn&#39;t at least partly drafted by a language model, that&#39;s the unusual case.&lt;/p&gt;
&lt;h2 id=&quot;how-i-do-it&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#how-i-do-it&quot;&gt;#&lt;/a&gt; How I do it&lt;/h2&gt;
&lt;p&gt;I usually write an excerpt or the rough idea myself, drop it into a prompt with markers like &lt;code&gt;(Claude note: expand on this with X or Y)&lt;/code&gt;, run &lt;a href=&quot;https://github.com/apurvrdx1/tagore&quot;&gt;&lt;code&gt;/tagore&lt;/code&gt;&lt;/a&gt; over the result, and then go back through it myself because tagore doesn&#39;t catch everything and I have opinions about how a sentence should land. I check every article myself, Vince the human ;-), before it ships.&lt;/p&gt;
&lt;p&gt;That&#39;s it. That&#39;s the whole workflow.&lt;/p&gt;
&lt;h2 id=&quot;is-that-shocking-does-it-kill-the-human-element&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#is-that-shocking-does-it-kill-the-human-element&quot;&gt;#&lt;/a&gt; Is that shocking? Does it kill the human element?&lt;/h2&gt;
&lt;p&gt;I don&#39;t think so.&lt;/p&gt;
&lt;p&gt;The part I write first is the load-bearing piece: the hot take, the structure, the specific examples, what I want to say. The model expands it, fills in transitions, suggests phrasing I wouldn&#39;t have reached for, and then I edit the parts that landed wrong. The opinions are mine. The choice to publish is mine.&lt;/p&gt;
&lt;p&gt;&amp;quot;Proofreader&amp;quot; undersells it. Proofreading is comma placement and dangling modifiers, and Claude does a lot more than that. It offers ways to phrase a paragraph that I&#39;d never have written but, once I read it, I recognise as the thing I was trying to say. &amp;quot;Ghostwriter&amp;quot; overshoots. Ghostwriters write the whole thing while the named author signs off. Neither fits.&lt;/p&gt;
&lt;p&gt;Co-writer is the word. Funny timing: Anthropic recently launched &lt;a href=&quot;https://www.anthropic.com/product/claude-cowork&quot;&gt;Claude Cowork&lt;/a&gt;, which is roughly the same framing: Claude as someone you collaborate with on the actual work, not as an oracle you query. Different product, same idea. Stop pretending the AI is a vending machine and start treating it like the smart colleague who&#39;s read everything and types fast.&lt;/p&gt;
&lt;h2 id=&quot;the-hot-take&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-hot-take&quot;&gt;#&lt;/a&gt; The hot take&lt;/h2&gt;
&lt;p&gt;The &amp;quot;AI vs human writing&amp;quot; frame is the wrong one in 2026. The interesting question is whether anyone thought about the writing before publishing. Whether a human typed it or a model drafted it matters much less.&lt;/p&gt;
&lt;p&gt;A model can produce a thousand competent-but-soulless posts a day. A human can also produce a thousand competent-but-soulless posts a day. Look at any content marketing pipeline from 2018. Neither is interesting.&lt;/p&gt;
&lt;p&gt;What I notice when I read good writing now is that the author had a point of view, picked the specific examples that matter, and trusted the reader. None of that depends on whether a human or a model wrote the first draft. Pure-human writing with no point of view is just as boring as pure-AI writing with no point of view. The collaboration model, where the human supplies opinion and judgment and the model supplies fluency, produces better stuff than either does alone, provided you do the judgment part instead of mass-accepting the suggestions.&lt;/p&gt;
&lt;p&gt;So no, AI didn&#39;t take the human out of writing. It took the typing out. Which, honestly, was always the boring part.&lt;/p&gt;
&lt;h2 id=&quot;the-practical-upside&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-practical-upside&quot;&gt;#&lt;/a&gt; The practical upside&lt;/h2&gt;
&lt;p&gt;This setup lets me publish faster, and from places where I&#39;d never normally bother to draft. An article like this one can start as a phone-typed lump of thoughts on the train, get expanded against my usual workflow, run through tagore, and end up reviewable by the time I&#39;m home. That used to be a half-day&#39;s work at a laptop. Now it&#39;s an evening with the laptop just for the edit pass.&lt;/p&gt;
&lt;p&gt;These articles exist because the friction of writing one dropped enough that I do it at all. That, on its own, would be enough to justify the workflow. Everything else is upside.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/using-ai-for-coding/&quot;&gt;My take on using AI for coding&lt;/a&gt;. The same thinking, applied to code instead of prose.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apurvrdx1/tagore&quot;&gt;&lt;code&gt;/tagore&lt;/code&gt; on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="claude"/><category term="ai"/><category term="writing"/><category term="writing-workflow"/><category term="opinion"/>
  </entry>
  
  <entry>
    <title>My take on using AI for coding</title>
    <link href="https://daal.cloud/articles/using-ai-for-coding/"/>
    <id>https://daal.cloud/articles/using-ai-for-coding/</id>
    <published>2026-05-07T00:00:00.000Z</published>
    <updated>2026-05-07T00:00:00.000Z</updated>
    <summary>Where I land on AI-assisted coding in 2026. Claude Code and the rest are powerful, but prompt steering, guardrails, spec-driven dev and the RALPH loop are what separate &quot;this works&quot; from &quot;I have no idea what I shipped&quot;.</summary>
    <content type="html">&lt;h2 id=&quot;what-i-actually-use&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#what-i-actually-use&quot;&gt;#&lt;/a&gt; What I actually use&lt;/h2&gt;
&lt;p&gt;Mostly &lt;a href=&quot;https://claude.ai/&quot;&gt;Claude&lt;/a&gt;. Claude Code in the terminal, Claude in the browser for one-off thinking. I&#39;ve tried &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursor&lt;/a&gt; and &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;GitHub Copilot&lt;/a&gt; too, and &lt;a href=&quot;https://openai.com/codex/&quot;&gt;Codex&lt;/a&gt; is competitive on a lot of tasks. Honestly the gap between the top tools is small compared to the gap between &amp;quot;good prompt with guardrails&amp;quot; and &amp;quot;vibe-coded one-shot&amp;quot;. The model matters less than I expected. The harness around it matters more.&lt;/p&gt;
&lt;h2 id=&quot;the-bottleneck-moved&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-bottleneck-moved&quot;&gt;#&lt;/a&gt; The bottleneck moved&lt;/h2&gt;
&lt;p&gt;When I wrote everything by hand, the slow step was typing and remembering syntax. Now the slow step is reviewing what the model produced and making sure it actually does what I asked, on the inputs I care about, without quietly breaking something else.&lt;/p&gt;
&lt;p&gt;So the work shifted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Less time writing boilerplate, glue code, first-pass implementations.&lt;/li&gt;
&lt;li&gt;More time reading diffs, writing tests for the diffs, and asking &amp;quot;wait, why did it touch that file?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn&#39;t a complaint. Reading and reviewing is higher-leverage than typing. But anyone who claims AI coding is &amp;quot;10x faster&amp;quot; without mentioning that the review burden went up too is selling something.&lt;/p&gt;
&lt;h2 id=&quot;prompt-steering-and-guardrails-do-most-of-the-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#prompt-steering-and-guardrails-do-most-of-the-work&quot;&gt;#&lt;/a&gt; Prompt steering and guardrails do most of the work&lt;/h2&gt;
&lt;p&gt;The biggest jump in output quality I&#39;ve seen comes from constraining the model up front:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; (or equivalent) at the repo root that spells out conventions, commands, and the shape of the codebase. Claude Code reads it on every session.&lt;/li&gt;
&lt;li&gt;Permissions, hooks, and explicit allow-lists so the agent can&#39;t run arbitrary destructive commands without asking.&lt;/li&gt;
&lt;li&gt;Sub-agents and skills for repeatable workflows. The article you&#39;re reading was created by a private skill of mine that enforces &amp;quot;branch from origin/main, run /tagore, open a PR, never merge&amp;quot;. It&#39;s not really a tool, just a workflow I trust wrapped in something Claude can invoke.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without those rails you end up with a very smart intern who occasionally &lt;code&gt;rm -rf&lt;/code&gt;s something they shouldn&#39;t. With them you get a very smart intern who actually checks before doing anything load-bearing.&lt;/p&gt;
&lt;h2 id=&quot;hey-claude-design-a-full-blown-fullstack-knowledge-base-make-no-mistakes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#hey-claude-design-a-full-blown-fullstack-knowledge-base-make-no-mistakes&quot;&gt;#&lt;/a&gt; &amp;quot;Hey Claude, design a full-blown fullstack knowledge base. Make no mistakes.&amp;quot;&lt;/h2&gt;
&lt;p&gt;That kind of prompt almost never produces what you actually want. It produces &lt;em&gt;something&lt;/em&gt;, usually a plausible-looking scaffold, but:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The model picks defaults that don&#39;t match your stack, your hosting, or your constraints.&lt;/li&gt;
&lt;li&gt;Trade-offs you&#39;d have argued about (auth model, data store, deploy target) get resolved silently.&lt;/li&gt;
&lt;li&gt;Six iterations later you&#39;ve got a folder full of code you didn&#39;t choose, and when something breaks you have no map of what depends on what.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ve watched this play out on side projects and on bigger work too. The further you let the AI run without you actually understanding the architecture, the more painful the eventual debugging session. Because at some point there &lt;em&gt;will&lt;/em&gt; be a debugging session, and the only person who can drive it is someone who understands how the pieces fit. If that&#39;s not you, you&#39;re stuck.&lt;/p&gt;
&lt;p&gt;So the takeaway isn&#39;t &amp;quot;don&#39;t use AI for new projects&amp;quot;. It&#39;s that understanding the architecture of your application matters more, not less, when AI writes most of the code. You don&#39;t need to type every line, but you do need to be able to redraw the box-and-arrow diagram from memory.&lt;/p&gt;
&lt;h2 id=&quot;what-works-better-specs-and-ralph&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#what-works-better-specs-and-ralph&quot;&gt;#&lt;/a&gt; What works better: specs and RALPH&lt;/h2&gt;
&lt;p&gt;The pattern I keep coming back to is spec-driven development plus a tight loop.&lt;/p&gt;
&lt;p&gt;Spec-driven means: before the model writes code, write down (or have it write down, then edit) what the thing should do, what it should not do, what the inputs and outputs are, and where the edges are. Save that spec next to the code. Then prompt against the spec, not against vibes.&lt;/p&gt;
&lt;p&gt;The loop part is what &lt;a href=&quot;https://awesomeclaude.ai/ralph-wiggum&quot;&gt;Geoffrey Huntley calls RALPH&lt;/a&gt;, short for Read, Analyse, Loop, Plan, Hone. The short version: don&#39;t ask for the whole feature in one shot. Ask the model to read the spec, look at the current state, plan the next small change, make it, then loop. Each pass is small enough to review honestly. Each pass updates the spec and the code together.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/blader/claude-mem&quot;&gt;claude-mem&lt;/a&gt; bakes a similar workflow in directly. &lt;code&gt;/make-plan&lt;/code&gt; produces a phased spec, &lt;code&gt;/do&lt;/code&gt; executes it pass by pass with subagents, and observations from each session feed the next one. One caveat: &lt;code&gt;/make-plan&lt;/code&gt; and &lt;code&gt;/do&lt;/code&gt; are barely documented. You kind of have to read the skill source to understand what they actually do. But once you do, they work brilliantly in my experience. There are alternatives, &lt;a href=&quot;https://github.com/gsd-build/get-shit-done&quot;&gt;get-shit-done&lt;/a&gt; being one of the more visible ones, but most of them feel bloated to me. claude-mem stays small and gets out of the way, which is what I want.&lt;/p&gt;
&lt;h2 id=&quot;what-i-d-tell-someone-starting-today&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#what-i-d-tell-someone-starting-today&quot;&gt;#&lt;/a&gt; What I&#39;d tell someone starting today&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Pick one tool (Claude Code, Cursor, whichever) and learn its harness deeply. Switching constantly is a tax.&lt;/li&gt;
&lt;li&gt;Always have a &lt;code&gt;CLAUDE.md&lt;/code&gt; (or equivalent) at the repo root. Update it when conventions change.&lt;/li&gt;
&lt;li&gt;Write the spec first. Even three bullet points beats nothing.&lt;/li&gt;
&lt;li&gt;Loop in small increments. Review every diff. Don&#39;t merge what you can&#39;t explain.&lt;/li&gt;
&lt;li&gt;When the model surprises you, good or bad, figure out &lt;em&gt;why&lt;/em&gt; before continuing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The tools are good. They&#39;re not magic, and they don&#39;t replace the work of knowing what you&#39;re building. The people I see getting the most out of them treat the AI as a fast collaborator that needs a clear spec and tight feedback, not as an oracle.&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.claude.com/en/docs/claude-code/overview&quot;&gt;Claude Code docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://awesomeclaude.ai/ralph-wiggum&quot;&gt;RALPH method on awesomeclaude.ai&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/blader/claude-mem&quot;&gt;claude-mem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gsd-build/get-shit-done&quot;&gt;get-shit-done&lt;/a&gt; for comparison&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="claude"/><category term="ai"/><category term="claude-code"/><category term="spec-driven-development"/><category term="ralph"/><category term="opinion"/>
  </entry>
  
  <entry>
    <title>Fixing Chromium virtual backgrounds on NVIDIA + Wayland</title>
    <link href="https://daal.cloud/articles/chromium-virtual-background-nvidia-wayland/"/>
    <id>https://daal.cloud/articles/chromium-virtual-background-nvidia-wayland/</id>
    <published>2026-05-04T00:00:00.000Z</published>
    <updated>2026-05-04T00:00:00.000Z</updated>
    <summary>Meet&#39;s background blur turns your video into a solid white rectangle on NVIDIA + Wayland Chromium. The cause is a Chromium ↔ NVIDIA EGL interop bug in the canvas captureStream path. Two flags work around it; pick based on monitor setup.</summary>
    <content type="html">&lt;p&gt;Switched on background blur in a Meet call and turned into a flat white rectangle. Camera works fine without the effect. The moment any background filter (blur or virtual background) is applied, everyone on the call (me in the self-preview included) sees a solid white frame where my video should be. Reproduces in any Chromium-based browser (Chromium, Vivaldi). Firefox is fine. Setup that hits this: NVIDIA proprietary driver (tested on 595.71.05, open kernel module branch, RTX 4090), KDE Plasma 6 on Wayland (&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt;), Flatpak Chromium 147.&lt;/p&gt;
&lt;h2 id=&quot;why-it-breaks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-breaks&quot;&gt;#&lt;/a&gt; Why it breaks&lt;/h2&gt;
&lt;p&gt;Meet&#39;s virtual background pipeline runs per frame:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;camera frame
  → MediaPipe TFLite WebGL segmentation
  → WebGL composite shader (frame + background, masked)
  → canvas.captureStream() → MediaStream
  → WebRTC encoder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;captureStream()&lt;/code&gt; hop has to share the WebGL output texture cross-process to the GPU process where the encoder runs. Chromium does this through its &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/docs/gpu/shared_image.md&quot;&gt;SharedImage&lt;/a&gt; abstraction, which on Linux maps to EGLImage / dmabuf. On NVIDIA&#39;s Wayland EGL implementation, the shared image arrives at the encoder side either uninitialised or wrongly synchronised, so reads come back as all-1s. Solid white frames.&lt;/p&gt;
&lt;p&gt;The shader runs and the composite is correct. The readback path is the broken thing.&lt;/p&gt;
&lt;p&gt;Firefox doesn&#39;t hit this because it doesn&#39;t use ANGLE or Chromium&#39;s SharedImage. Its &lt;code&gt;captureStream&lt;/code&gt; goes through a different path (often a CPU readback) that never exercises the broken NVIDIA EGL interop.&lt;/p&gt;
&lt;h2 id=&quot;fix&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#fix&quot;&gt;#&lt;/a&gt; Fix&lt;/h2&gt;
&lt;p&gt;Two flags work. Pick one based on monitor setup.&lt;/p&gt;
&lt;h3 id=&quot;option-1-ozone-platform-x11-preferred-on-a-desktop-with-same-scale-monitors&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#option-1-ozone-platform-x11-preferred-on-a-desktop-with-same-scale-monitors&quot;&gt;#&lt;/a&gt; Option 1: &lt;code&gt;--ozone-platform=x11&lt;/code&gt; (preferred on a desktop with same-scale monitors)&lt;/h3&gt;
&lt;p&gt;Runs Chromium under XWayland. GPU compositing stays on, WebGL/canvas/video decode stay full-speed, PipeWire screen sharing still works (it&#39;s independent of Ozone). Cost:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XWayland is integer-scale only. No fractional HiDPI, no proper per-monitor scaling.&lt;/li&gt;
&lt;li&gt;Drag-and-drop and clipboard with native Wayland apps can be slightly flaky.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fine on a desktop with one or two monitors at the same scale. Annoying if you mix a 4K-at-200% panel with a 1080p-at-100% one.&lt;/p&gt;
&lt;h3 id=&quot;option-2-disable-gpu-compositing-preferred-on-laptops-or-mixed-scale-monitors&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#option-2-disable-gpu-compositing-preferred-on-laptops-or-mixed-scale-monitors&quot;&gt;#&lt;/a&gt; Option 2: &lt;code&gt;--disable-gpu-compositing&lt;/code&gt; (preferred on laptops or mixed-scale monitors)&lt;/h3&gt;
&lt;p&gt;Wayland-native integration kept. Compositor runs on CPU. WebGL and canvas individual draws still hit the GPU; only the final composite is software. Cost:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuous extra CPU usage.&lt;/li&gt;
&lt;li&gt;Less smooth scrolling on heavy pages.&lt;/li&gt;
&lt;li&gt;Worse battery on laptops.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Genuinely unnoticeable on a 4090 desktop. Can matter on a laptop.&lt;/p&gt;
&lt;h3 id=&quot;how-to-apply-flatpak-chromium&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#how-to-apply-flatpak-chromium&quot;&gt;#&lt;/a&gt; How to apply (Flatpak Chromium)&lt;/h3&gt;
&lt;p&gt;Don&#39;t edit the system .desktop file. Flatpak rewrites it on every update. Copy it to &lt;code&gt;~/.local/share/applications/&lt;/code&gt; instead and edit the local copy. KDE picks up the local override automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp /var/lib/flatpak/exports/share/applications/org.chromium.Chromium.desktop &#92;
   ~/.local/share/applications/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the chosen flag to all three &lt;code&gt;Exec=&lt;/code&gt; lines (main, &amp;quot;New Window&amp;quot; action, &amp;quot;New Incognito Window&amp;quot; action). For Option 1 the main line becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;Exec=/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=/app/bin/chromium --file-forwarding org.chromium.Chromium --ozone-platform=x11 @@u %U @@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quit Chromium fully and relaunch. Verify in &lt;code&gt;chrome://gpu&lt;/code&gt;: &amp;quot;Ozone platform&amp;quot; should now read &lt;code&gt;x11&lt;/code&gt;, or under Option 2 &amp;quot;Compositing&amp;quot; should read software-only.&lt;/p&gt;
&lt;p&gt;For Vivaldi the equivalent lives in &lt;code&gt;~/.config/vivaldi-stable.conf&lt;/code&gt;. Same flags, see &lt;a href=&quot;/articles/vivaldi-persistent-flags/&quot;&gt;Persistent CLI flags for Vivaldi on Linux&lt;/a&gt; for the conf-file syntax.&lt;/p&gt;
&lt;h2 id=&quot;things-i-tried-that-didn-t-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#things-i-tried-that-didn-t-work&quot;&gt;#&lt;/a&gt; Things I tried that didn&#39;t work&lt;/h2&gt;
&lt;p&gt;For future-me, so I don&#39;t bisect them again. None of these fix it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--disable-features=WebRtcVideoCaptureGpuMemoryBuffer&lt;/code&gt;. Disables zero-copy GPU buffers for camera capture. The camera path is fine, the bug doesn&#39;t live here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-features=WebRtcVideoCaptureGpuMemoryBuffer,VaapiVideoDecoder&lt;/code&gt;. Adds VA-API decode disable on top. Same outcome.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--use-angle=gl&lt;/code&gt;. Forces ANGLE off Vulkan onto plain GL. The ANGLE backend isn&#39;t the broken thing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-features=WebRTCPipeWireCamera&lt;/code&gt;. Falls back to V4L2 capture. Doesn&#39;t matter; camera path is fine.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-features=Vulkan&lt;/code&gt;. Kills Vulkan everywhere. The shared-image bug doesn&#39;t go through Vulkan in this configuration.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-accelerated-2d-canvas&lt;/code&gt;. Forces 2D canvas to CPU. MediaPipe outputs from a WebGL canvas, so this is the wrong canvas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only the two flags above cross the broken path entirely (Ozone on X11 sidesteps the NVIDIA Wayland EGL path; &lt;code&gt;--disable-gpu-compositing&lt;/code&gt; forces a CPU readback that doesn&#39;t need shared images at all).&lt;/p&gt;
&lt;h2 id=&quot;the-real-fix-lives-upstream&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-real-fix-lives-upstream&quot;&gt;#&lt;/a&gt; The real fix lives upstream&lt;/h2&gt;
&lt;p&gt;This is a Chromium ↔ NVIDIA Wayland EGL interop bug. Almost certainly already tracked in &lt;a href=&quot;https://issues.chromium.org/&quot;&gt;issues.chromium.org&lt;/a&gt;. Worth searching with terms like &lt;em&gt;chromium wayland nvidia canvas captureStream white&lt;/em&gt; or &lt;em&gt;MediaPipe NVIDIA Wayland virtual background&lt;/em&gt; before filing anything new.&lt;/p&gt;
&lt;p&gt;Could also end up being a Flathub packaging fix. The Chromium flatpak already pre-applies &lt;code&gt;--enable-features=WebRTCPipeWireCapturer&lt;/code&gt; for exactly this kind of platform-specific compatibility patch, so a future build could ship one of these flags by default for NVIDIA users.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/vivaldi-persistent-flags/&quot;&gt;Persistent CLI flags for Vivaldi on Linux&lt;/a&gt;. Same flags, applied via Vivaldi&#39;s conf-file mechanism.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Why the browser is a Flatpak in the first place.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt;. Another Flatpak browser quirk worth knowing about.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;Chromium command-line switches&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/docs/gpu/shared_image.md&quot;&gt;Chromium SharedImage design overview&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ozone_overview.md&quot;&gt;Ozone platform abstraction&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ai.google.dev/edge/mediapipe/solutions/vision/image_segmenter&quot;&gt;MediaPipe selfie segmentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="chromium"/><category term="nvidia"/><category term="wayland"/><category term="webrtc"/><category term="bazzite"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Making CoolerControl&#39;s GUI start on login</title>
    <link href="https://daal.cloud/articles/coolercontrol-autostart/"/>
    <id>https://daal.cloud/articles/coolercontrol-autostart/</id>
    <published>2026-05-03T00:00:00.000Z</published>
    <updated>2026-05-03T00:00:00.000Z</updated>
    <summary>CoolerControl ships a system daemon and a separate Qt GUI. The daemon controls fans at boot regardless of login, but the rpm doesn&#39;t install an autostart .desktop for the GUI, so the tray icon never appears. Drop one in `~/.config/autostart/` (or use Plasma&#39;s System Settings → Autostart) to fix it.</summary>
    <content type="html">&lt;p&gt;CoolerControl is two binaries: a system daemon (&lt;code&gt;coolercontrold&lt;/code&gt;) and a Qt GUI client (&lt;code&gt;coolercontrol&lt;/code&gt;). The daemon does the actual work (pump curves, fan curves, temperature monitoring), and the rpm enables it as a systemd service, so cooling stays correct at boot whether you log in or not. The GUI is just a viewer for the daemon&#39;s API plus the configuration UI.&lt;/p&gt;
&lt;p&gt;When you reboot and there&#39;s no tray icon, your fans are still being controlled. You&#39;re missing a UI, not a fan curve. But you&#39;ll still want it back.&lt;/p&gt;
&lt;p&gt;Notes below are written for KDE Plasma (the default Bazzite spin) on Wayland. GNOME-specific deltas are called out where they matter.&lt;/p&gt;
&lt;h2 id=&quot;why-it-doesn-t-autostart&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-doesn-t-autostart&quot;&gt;#&lt;/a&gt; Why it doesn&#39;t autostart&lt;/h2&gt;
&lt;p&gt;The Fedora rpm (&lt;code&gt;coolercontrol-4.2.0-1.fc44.x86_64&lt;/code&gt; at the time of writing) installs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/usr/lib/systemd/system/coolercontrold.service&lt;/code&gt;, the daemon, enabled by default.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share/applications/org.coolercontrol.CoolerControl.desktop&lt;/code&gt;, a launcher entry for the apps menu.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What&#39;s missing: no &lt;code&gt;/etc/xdg/autostart/coolercontrol.desktop&lt;/code&gt;, and the package doesn&#39;t drop anything into &lt;code&gt;~/.config/autostart/&lt;/code&gt; on first run either. The entry under &lt;code&gt;applications/&lt;/code&gt; puts CoolerControl in your apps menu. XDG autostart loaders don&#39;t look there.&lt;/p&gt;
&lt;p&gt;Different from &lt;a href=&quot;/articles/kopia-flatpak-autostart/&quot;&gt;Making KopiaUI start on login when installed as a Flatpak&lt;/a&gt;, where an in-app toggle exists but silently writes into a sandboxed config dir. Here there&#39;s no toggle that writes a .desktop file at all; the package just doesn&#39;t ship autostart.&lt;/p&gt;
&lt;h2 id=&quot;fix-plasma-click-path&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#fix-plasma-click-path&quot;&gt;#&lt;/a&gt; Fix (Plasma, click path)&lt;/h2&gt;
&lt;p&gt;If you&#39;d rather not edit dotfiles: &lt;em&gt;System Settings → Autostart → Add → Add Application → CoolerControl → OK&lt;/em&gt;. Plasma writes a &lt;code&gt;.desktop&lt;/code&gt; under &lt;code&gt;~/.config/autostart/&lt;/code&gt; for you and applies it on the next session start.&lt;/p&gt;
&lt;h2 id=&quot;fix-any-de-file-path&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#fix-any-de-file-path&quot;&gt;#&lt;/a&gt; Fix (any DE, file path)&lt;/h2&gt;
&lt;p&gt;Drop a minimal autostart entry on the host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# ~/.config/autostart/coolercontrol.desktop
[Desktop Entry]
Type=Application
Name=CoolerControl
Comment=Monitor and control your cooling device
Exec=coolercontrol
Icon=org.coolercontrol.CoolerControl
Terminal=false
StartupNotify=false
X-GNOME-Autostart-enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plasma needs nothing more than a valid XDG &lt;code&gt;.desktop&lt;/code&gt; file in &lt;code&gt;~/.config/autostart/&lt;/code&gt;. Its session manager honours the freedesktop spec directly. The &lt;code&gt;X-GNOME-Autostart-enabled=true&lt;/code&gt; line is a GNOME-specific extension; Plasma ignores it, but leaving it in keeps the file portable if you ever switch DE or copy it to another machine.&lt;/p&gt;
&lt;p&gt;Verify after a reboot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pgrep -af coolercontrol
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see two processes: &lt;code&gt;coolercontrold&lt;/code&gt; under the system slice (the daemon) and &lt;code&gt;coolercontrol&lt;/code&gt; under your user session (the GUI).&lt;/p&gt;
&lt;h2 id=&quot;get-it-to-start-in-the-tray&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#get-it-to-start-in-the-tray&quot;&gt;#&lt;/a&gt; Get it to start in the tray&lt;/h2&gt;
&lt;p&gt;By default the GUI opens its main window on launch, which is annoying as an autostart. There&#39;s no &lt;code&gt;--start-in-tray&lt;/code&gt; CLI flag despite forum threads claiming there is. &lt;code&gt;coolercontrol --help&lt;/code&gt; lists &lt;code&gt;-h&lt;/code&gt;, &lt;code&gt;-v&lt;/code&gt;, &lt;code&gt;-d&lt;/code&gt;, &lt;code&gt;--full-debug&lt;/code&gt;, &lt;code&gt;--disable-gpu&lt;/code&gt;, &lt;code&gt;--chromium-flags&lt;/code&gt;, &lt;code&gt;--clear-cache&lt;/code&gt;. That&#39;s it.&lt;/p&gt;
&lt;p&gt;Tray-on-launch is a setting inside the app: &lt;em&gt;Settings → Start in Tray&lt;/em&gt;. Enable it once with the GUI open, then future autostarts come up minimised to the system tray.&lt;/p&gt;
&lt;p&gt;Plasma&#39;s tray shows the CoolerControl icon out of the box (StatusNotifierItem support is native). GNOME needs the &lt;strong&gt;AppIndicator and KStatusNotifierItem Support&lt;/strong&gt; extension. Without it, the GUI is running but invisible: no main window, no tray icon, just a process you can only see in &lt;code&gt;pgrep&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;when-it-still-doesn-t-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-it-still-doesn-t-work&quot;&gt;#&lt;/a&gt; When it still doesn&#39;t work&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Daemon isn&#39;t running.&lt;/strong&gt; &lt;code&gt;systemctl status coolercontrold.service&lt;/code&gt;. If it&#39;s disabled, &lt;code&gt;sudo systemctl enable --now coolercontrold.service&lt;/code&gt;. The GUI will sit at &amp;quot;Connecting to daemon&amp;quot; forever without it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GUI launches but pegs at &amp;quot;Connecting to daemon&amp;quot; with the daemon up.&lt;/strong&gt; The daemon listens on &lt;code&gt;127.0.0.1:11987&lt;/code&gt; over HTTPS with a self-signed cert. If you&#39;ve tightened the local firewall or replaced the cert, that&#39;s where to start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wayland session, GUI window flashes on login then closes.&lt;/strong&gt; Check &lt;code&gt;journalctl --user -b | grep -i coolercontrol&lt;/code&gt;. Usually a Qt platform plugin or GPU acceleration issue. Add &lt;code&gt;--disable-gpu&lt;/code&gt; to the &lt;code&gt;Exec=&lt;/code&gt; line as a workaround.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plasma&#39;s &lt;em&gt;System Settings → Autostart&lt;/em&gt; lists the entry but it doesn&#39;t run.&lt;/strong&gt; Check perms on the desktop file. It must be readable by your user. &lt;code&gt;chmod 644 ~/.config/autostart/coolercontrol.desktop&lt;/code&gt;. Same applies to GNOME&#39;s &lt;em&gt;Tweaks → Startup Applications&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-not-just-the-system-desktop-file&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-not-just-the-system-desktop-file&quot;&gt;#&lt;/a&gt; Why not just the system desktop file&lt;/h2&gt;
&lt;p&gt;You might be tempted to copy &lt;code&gt;/usr/share/applications/org.coolercontrol.CoolerControl.desktop&lt;/code&gt; into &lt;code&gt;/etc/xdg/autostart/&lt;/code&gt;. Don&#39;t, on Bazzite or any other rpm-ostree atomic system: &lt;code&gt;/etc&lt;/code&gt; is layered, but &lt;code&gt;/usr&lt;/code&gt; is read-only and gets rebuilt on every rebase. A user-level autostart entry under &lt;code&gt;~/.config/autostart/&lt;/code&gt; survives both system updates and rebases, and doesn&#39;t need root.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/kopia-flatpak-autostart/&quot;&gt;Making KopiaUI start on login when installed as a Flatpak&lt;/a&gt;. Same family of &amp;quot;Linux desktop autostart is annoying&amp;quot; problem, different root cause: Flatpak sandbox vs missing package autostart entry.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Why this matters more on immutable Fedora: &lt;code&gt;/usr/share/applications/&lt;/code&gt; is read-only, so you can&#39;t fix this by editing the system desktop file.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt;. Another sandbox/permissions wrinkle in the same family of desktop-Linux papercuts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.coolercontrol.org/&quot;&gt;CoolerControl documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html&quot;&gt;XDG Autostart specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/coolercontrol/coolercontrol&quot;&gt;CoolerControl GitLab repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="coolercontrol"/><category term="hardware"/><category term="bazzite"/><category term="kde"/><category term="plasma"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Making KopiaUI start on login when installed as a Flatpak</title>
    <link href="https://daal.cloud/articles/kopia-flatpak-autostart/"/>
    <id>https://daal.cloud/articles/kopia-flatpak-autostart/</id>
    <published>2026-04-28T00:00:00.000Z</published>
    <updated>2026-05-03T00:00:00.000Z</updated>
    <summary>KopiaUI&#39;s built-in &quot;Launch at startup&quot; toggle silently fails under Flatpak. The sandbox can&#39;t write to host `~/.config/autostart/`. Fix is a hand-rolled autostart `.desktop` file with a minimal `flatpak run` Exec line.</summary>
    <content type="html">&lt;p&gt;KopiaUI installed as a Flatpak (&lt;code&gt;io.kopia.KopiaUI&lt;/code&gt;) has a &amp;quot;Launch at startup&amp;quot; toggle in &lt;em&gt;Settings → Preferences&lt;/em&gt;. Under Flatpak, ticking it does nothing useful. The app reports success, but no autostart entry lands on the host and Kopia just doesn&#39;t come back after reboot.&lt;/p&gt;
&lt;p&gt;Sandbox problem, not a Kopia bug.&lt;/p&gt;
&lt;p&gt;Notes below assume KDE Plasma on Wayland (default Bazzite spin). The fix file itself is DE-agnostic; GNOME-specific deltas are called out where they matter.&lt;/p&gt;
&lt;h2 id=&quot;why-it-breaks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-breaks&quot;&gt;#&lt;/a&gt; Why it breaks&lt;/h2&gt;
&lt;p&gt;The toggle writes to &lt;code&gt;$XDG_CONFIG_HOME/autostart/kopia-ui.desktop&lt;/code&gt;. Under Flatpak, &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt; is rewritten to the per-app sandbox directory (&lt;code&gt;~/.var/app/io.kopia.KopiaUI/config/&lt;/code&gt;), and the sandbox doesn&#39;t get &lt;code&gt;--filesystem=xdg-config/autostart:create&lt;/code&gt; by default. The file is &amp;quot;written&amp;quot; inside the sandbox where the session autostart loader never looks.&lt;/p&gt;
&lt;p&gt;The flatpak-exported launcher at &lt;code&gt;/var/lib/flatpak/exports/share/applications/io.kopia.KopiaUI.desktop&lt;/code&gt; is also a bad copy-paste source. Its &lt;code&gt;Exec=&lt;/code&gt; line is built for URL-handler launches:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exec=/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=kopia-ui --file-forwarding io.kopia.KopiaUI @@u %U @@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--file-forwarding ... @@u %U @@&lt;/code&gt; block expects URL arguments. With no URLs at autostart, what actually happens depends on the session manager. Anything from a stray &amp;quot;Open File&amp;quot; prompt to silently failing to background.&lt;/p&gt;
&lt;p&gt;This is also why the click-path through Plasma&#39;s &lt;em&gt;System Settings → Autostart → Add Application&lt;/em&gt; doesn&#39;t save you: it copies the exported &lt;code&gt;.desktop&lt;/code&gt; into your autostart dir verbatim, broken &lt;code&gt;Exec&lt;/code&gt; and all. Same trap on GNOME&#39;s &lt;em&gt;Tweaks → Startup Applications&lt;/em&gt;. You need the hand-rolled file below.&lt;/p&gt;
&lt;h2 id=&quot;fix&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#fix&quot;&gt;#&lt;/a&gt; Fix&lt;/h2&gt;
&lt;p&gt;Drop a minimal autostart entry on the host:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# ~/.config/autostart/kopia-ui.desktop
[Desktop Entry]
Type=Application
Version=1.0
Name=KopiaUI
Comment=Fast and secure open source backup
Exec=flatpak run io.kopia.KopiaUI
StartupNotify=false
Terminal=false
X-GNOME-Autostart-enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The file is plain XDG autostart-spec, so Plasma&#39;s session manager picks it up directly from &lt;code&gt;~/.config/autostart/&lt;/code&gt; and runs it on login. The &lt;code&gt;X-GNOME-Autostart-enabled=true&lt;/code&gt; line is GNOME-specific extra metadata; Plasma ignores it, but leaving it in keeps the file portable if you also use a GNOME session somewhere.&lt;/p&gt;
&lt;p&gt;Verify after a reboot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pgrep -af &#39;kopia-ui|io.kopia.KopiaUI&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see a &lt;code&gt;flatpak run&lt;/code&gt; wrapper and the actual &lt;code&gt;kopia-ui&lt;/code&gt; process under it.&lt;/p&gt;
&lt;h2 id=&quot;when-it-still-doesn-t-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-it-still-doesn-t-work&quot;&gt;#&lt;/a&gt; When it still doesn&#39;t work&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Plasma&#39;s &lt;em&gt;System Settings → Autostart&lt;/em&gt; lists it but it doesn&#39;t run.&lt;/strong&gt; Check &lt;code&gt;~/.config/autostart/kopia-ui.desktop&lt;/code&gt; perms. It must be readable by your user. &lt;code&gt;chmod 644&lt;/code&gt; it. Same applies to GNOME&#39;s &lt;em&gt;Tweaks → Startup Applications&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tray icon never appears on Plasma.&lt;/strong&gt; Plasma has native StatusNotifierItem support, so this should just work. If it doesn&#39;t, make sure the system tray widget is on your panel and configured to show all icons, not just an allow-list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tray icon never appears on GNOME.&lt;/strong&gt; GNOME has no native system tray. Install the &lt;strong&gt;AppIndicator and KStatusNotifierItem Support&lt;/strong&gt; extension. Without it, KopiaUI runs but minimises to nothing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kopia repo not unlocked at autostart.&lt;/strong&gt; By design. Kopia won&#39;t decrypt the repo without the password. Either configure the OS keyring integration in KopiaUI, or accept that you need to click through the unlock prompt once per session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-not-the-in-app-toggle&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-not-the-in-app-toggle&quot;&gt;#&lt;/a&gt; Why not the in-app toggle&lt;/h2&gt;
&lt;p&gt;Even if a future Kopia release adds &lt;code&gt;--filesystem=xdg-config/autostart:create&lt;/code&gt; to its Flatpak manifest, the in-app toggle still produces the messy &lt;code&gt;Exec&lt;/code&gt; line from the exported launcher (it copies the system desktop entry verbatim). The Plasma and GNOME click-path autostart dialogs do the same thing. The hand-rolled file above is cleaner and survives Kopia upgrades because nothing on the package side touches &lt;code&gt;~/.config/autostart/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Why Flatpak is the default install path on immutable Fedora.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/coolercontrol-autostart/&quot;&gt;Making CoolerControl&amp;apos;s GUI start on login&lt;/a&gt;. Same family of &amp;quot;Linux desktop autostart is annoying&amp;quot; problem, different root cause: rpm package ships no autostart entry at all, instead of one written into a sandboxed path.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt;. Same shape of problem: Flatpak sandbox isolation breaking an in-app feature that assumes host-level filesystem access.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/selinux-rsync-systemd-timer/&quot;&gt;Running rsync from a systemd timer on Bazzite &amp;lpar;SELinux rsync&amp;lowbar;t domain&amp;rpar;&lt;/a&gt;. If you&#39;re running Kopia alongside other backup automation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kopia.io/docs/installation/&quot;&gt;Kopia documentation — Linux installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html&quot;&gt;XDG Autostart specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.flatpak.org/en/latest/sandbox-permissions.html&quot;&gt;Flatpak sandbox permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="flatpak"/><category term="kopia"/><category term="backups"/><category term="bazzite"/><category term="kde"/><category term="plasma"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Running rsync from a systemd timer on Bazzite (SELinux rsync_t domain)</title>
    <link href="https://daal.cloud/articles/selinux-rsync-systemd-timer/"/>
    <id>https://daal.cloud/articles/selinux-rsync-systemd-timer/</id>
    <published>2026-04-28T00:00:00.000Z</published>
    <updated>2026-04-28T00:00:00.000Z</updated>
    <summary>You&#39;ll probably never need this exact setup. The reason it&#39;s written down is the SELinux mechanics underneath: domain transitions, audit2allow&#39;s blind spots, dontaudit, label inheritance, and how to actually figure out why SELinux is blocking you instead of just flipping a boolean.</summary>
    <content type="html">&lt;p&gt;Likely you will never do this. But the SELinux gotchas I hit along the way are worth writing down even if the original task isn&#39;t. So treat this less as a recipe and more as a tour of how SELinux actually behaves when something it didn&#39;t expect tries to read or write under &lt;code&gt;/home&lt;/code&gt;, and how to figure out &lt;em&gt;why&lt;/em&gt; it&#39;s blocking you instead of just flipping a boolean and moving on.&lt;/p&gt;
&lt;p&gt;For context: I don&#39;t normally run SELinux. On a desktop, why would you? ;-) The benefit-to-friction ratio just isn&#39;t there for me on a single-user box. But Bazzite ships it enforcing by default, and I&#39;d rather work with the policy than turn it off, so when this hit I leaned in.&lt;/p&gt;
&lt;p&gt;The setup that triggered it: two user accounts on the same Bazzite install, call them username1 and username2. Username2 already had a working backup. Username1 wanted their savegames included in it. The obvious move would be to set up backups on username1 too, but I already had this thing running on username2, so instead I wrote a systemd timer that runs rsync as root from username1&#39;s home into a folder under username2, and let username2&#39;s existing backup pick it up from there. I like a challenge but also don&#39;t want to reinvent the wheel. While writing this article I&#39;m already regretting it. But hey, here we are.&lt;/p&gt;
&lt;p&gt;And SELinux had thoughts.&lt;/p&gt;
&lt;p&gt;A systemd timer that runs &lt;code&gt;rsync&lt;/code&gt; as root does not behave like &lt;code&gt;rsync&lt;/code&gt; from a sudo shell, even though the UID is the same. Fedora&#39;s SELinux policy labels &lt;code&gt;/usr/bin/rsync&lt;/code&gt; as &lt;code&gt;rsync_exec_t&lt;/code&gt; and ships a &lt;code&gt;type_transition&lt;/code&gt; that moves the process into the confined &lt;code&gt;rsync_t&lt;/code&gt; domain whenever it&#39;s launched from a system context (&lt;code&gt;init_t&lt;/code&gt;, &lt;code&gt;initrc_t&lt;/code&gt;, etc). &lt;code&gt;rsync_t&lt;/code&gt; is intentionally narrow. It&#39;s the domain for the rsync server, not for ad-hoc file copies. Reading or writing anything under &lt;code&gt;/home&lt;/code&gt; or &lt;code&gt;/var/home&lt;/code&gt; is denied by default.&lt;/p&gt;
&lt;p&gt;A sudo shell stays in &lt;code&gt;unconfined_t&lt;/code&gt; and skips the transition. That&#39;s why &amp;quot;but it works when I run it by hand&amp;quot; is the usual entry point to this rabbit hole.&lt;/p&gt;
&lt;p&gt;Below: the Bazzite-specific traps I hit, plus how I cut a small local SELinux module for it instead of flipping the &lt;code&gt;rsync_full_access&lt;/code&gt; boolean.&lt;/p&gt;
&lt;h2 id=&quot;confirm-the-transition-is-happening&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#confirm-the-transition-is-happening&quot;&gt;#&lt;/a&gt; Confirm the transition is happening&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ausearch -m AVC -ts recent | grep rsync_t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see &lt;code&gt;scontext=system_u:system_r:rsync_t:s0&lt;/code&gt; paired with denied operations against &lt;code&gt;user_home_t&lt;/code&gt; or &lt;code&gt;data_home_t&lt;/code&gt;, that&#39;s the transition. Outside SELinux, a quick check is &lt;code&gt;cat /proc/&amp;lt;pid&amp;gt;/attr/current&lt;/code&gt; on the running rsync.&lt;/p&gt;
&lt;h2 id=&quot;two-ways-to-fix-it&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#two-ways-to-fix-it&quot;&gt;#&lt;/a&gt; Two ways to fix it&lt;/h2&gt;
&lt;h3 id=&quot;a-the-blunt-boolean&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#a-the-blunt-boolean&quot;&gt;#&lt;/a&gt; A. The blunt boolean&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo setsebool -P rsync_full_access on
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Persistent across reboots. Lets &lt;code&gt;rsync_t&lt;/code&gt; access most non-security files anywhere. Fine for a personal box, too wide for anything shared.&lt;/p&gt;
&lt;h3 id=&quot;b-a-scoped-local-policy-module&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#b-a-scoped-local-policy-module&quot;&gt;#&lt;/a&gt; B. A scoped local policy module&lt;/h3&gt;
&lt;p&gt;What you want when rsync only needs to touch a specific subtree.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Put just the &lt;code&gt;rsync_t&lt;/code&gt; domain into permissive so a full sync runs end-to-end and logs every denial it would have hit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo semanage permissive -a rsync_t
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trigger the unit, ideally with a stale file in the destination so &lt;code&gt;--delete&lt;/code&gt; actually fires:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo touch /path/to/dest/STALE.tmp
sudo systemctl start your-rsync.service
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pull the AVCs and generate a module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ausearch -m AVC --start recent | audit2allow -M your_module
sudo semodule -i your_module.pp
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drop permissive and verify under enforcing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo semanage permissive -d rsync_t
sudo systemctl start your-rsync.service
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For my use case (rsync mirror with &lt;code&gt;--delete&lt;/code&gt; and &lt;code&gt;--chown&lt;/code&gt;, source under one user&#39;s home, destination under another), the rules I needed were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;allow rsync_t data_home_t:dir { getattr open read remove_name search setattr write add_name };
allow rsync_t data_home_t:file { getattr setattr unlink read open create write };
allow rsync_t gconf_home_t:dir search;
allow rsync_t user_home_dir_t:dir search;
allow rsync_t user_home_t:dir search;
allow rsync_t self:capability { dac_override chown fowner fsetid };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing that bit me: &lt;code&gt;data_home_t:file setattr&lt;/code&gt;. &lt;code&gt;audit2allow&lt;/code&gt; only suggests it once rsync has actually tried to update mtimes on an existing destination file, so the first permissive pass usually misses it and you find out the next time you run.&lt;/p&gt;
&lt;h2 id=&quot;debugging-traps-that-ate-hours&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#debugging-traps-that-ate-hours&quot;&gt;#&lt;/a&gt; Debugging traps that ate hours&lt;/h2&gt;
&lt;h3 id=&quot;audit2allow-only-sees-avcs-that-already-happened&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#audit2allow-only-sees-avcs-that-already-happened&quot;&gt;#&lt;/a&gt; audit2allow only sees AVCs that already happened&lt;/h3&gt;
&lt;p&gt;If rsync exits on the first denial, you only get that first denial in the audit log. Flip &lt;code&gt;rsync_t&lt;/code&gt; permissive, let the run finish end to end, then collect. Otherwise the module ships incomplete and every enforcing run after that hands you one more rule, one denial at a time.&lt;/p&gt;
&lt;h3 id=&quot;dontaudit-rules-silently-hide-denials&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#dontaudit-rules-silently-hide-denials&quot;&gt;#&lt;/a&gt; dontaudit rules silently hide denials&lt;/h3&gt;
&lt;p&gt;The default policy contains &lt;code&gt;dontaudit&lt;/code&gt; rules that suppress AVCs the policy author considered noise. When a denial is real but invisible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo semodule -DB   # disable all dontaudit, rebuild policy
# ... reproduce, debug ...
sudo semodule -B    # restore
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;bazzite-s-default-audit-rule-suppresses-a-class-of-events&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#bazzite-s-default-audit-rule-suppresses-a-class-of-events&quot;&gt;#&lt;/a&gt; Bazzite&#39;s default audit rule suppresses a class of events&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/etc/audit/rules.d/audit.rules&lt;/code&gt; ships with &lt;code&gt;-a never,task&lt;/code&gt;, which can hide events you&#39;d expect to see. If AVCs are still missing after disabling dontaudit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo auditctl -D                  # clear runtime audit rules
# ... reproduce, ausearch, debug ...
sudo augenrules --load            # restore from rules.d
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;files-inherit-the-label-of-whichever-process-created-them&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#files-inherit-the-label-of-whichever-process-created-them&quot;&gt;#&lt;/a&gt; Files inherit the label of whichever process created them&lt;/h3&gt;
&lt;p&gt;If your initial seed ran from a sudo shell (&lt;code&gt;unconfined_t&lt;/code&gt;) and the later periodic runs come from the timer (&lt;code&gt;rsync_t&lt;/code&gt;), the destination directory and the files inside can end up with different SELinux types. The dir-class denials in &lt;code&gt;audit2allow&lt;/code&gt; won&#39;t match the file-class denials, which is confusing for about an hour. Fix with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chcon -R -t data_home_t /path/to/dest
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;authoring-a-te-file-by-hand&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#authoring-a-te-file-by-hand&quot;&gt;#&lt;/a&gt; Authoring a &lt;code&gt;.te&lt;/code&gt; file by hand&lt;/h3&gt;
&lt;p&gt;When you want to skip &lt;code&gt;audit2allow&lt;/code&gt; and write the rules yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo checkmodule -M -m -o your_module.mod your_module.te
sudo semodule_package -o your_module.pp -m your_module.mod
sudo semodule -i your_module.pp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;sudo semodule -l | grep your_module&lt;/code&gt; confirms install. &lt;code&gt;sudo semodule -r your_module&lt;/code&gt; removes it.&lt;/p&gt;
&lt;h2 id=&quot;bazzite-specifics-worth-flagging&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#bazzite-specifics-worth-flagging&quot;&gt;#&lt;/a&gt; Bazzite specifics worth flagging&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;rsync_t&lt;/code&gt; transition is Fedora policy, nothing Bazzite-specific. Same workflow applies on Silverblue, Kinoite, Bluefin, plain Fedora, and RHEL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/lib/selinux/targeted/active/modules&lt;/code&gt; isn&#39;t where modules live on rpm-ostree systems; storage sits elsewhere under the ostree deployment. Use &lt;code&gt;semodule -l&lt;/code&gt; and &lt;code&gt;semodule -E &amp;lt;name&amp;gt;&lt;/code&gt; to inspect rather than &lt;code&gt;find&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Audit rules persist across &lt;code&gt;rpm-ostree&lt;/code&gt; upgrades because &lt;code&gt;/etc&lt;/code&gt; is a real directory, not part of the immutable image.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-bother-with-the-module-instead-of-the-boolean&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-bother-with-the-module-instead-of-the-boolean&quot;&gt;#&lt;/a&gt; Why bother with the module instead of the boolean&lt;/h2&gt;
&lt;p&gt;The boolean would have saved me hours. The module is safer: it lists exactly which types &lt;code&gt;rsync_t&lt;/code&gt; is allowed to touch and nothing else. On a personal machine the boolean is probably fine. If you hit this on a server, now you know how to fix it. Although... if you really want to copy things between Username1 and Username2 on a server, I have other questions for you ;-)&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/ssm-shell-apparmor-systemd-run/&quot;&gt;Escaping the AWS SSM AppArmor profile with systemd-run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/rpm-ostree-rebase-cheatsheet/&quot;&gt;rpm-ostree&amp;colon; rebase&amp;comma; pin&amp;comma; rollback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.fedoraproject.org/en-US/quick-docs/selinux/&quot;&gt;SELinux User&#39;s and Administrator&#39;s Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/1/audit2allow&quot;&gt;&lt;code&gt;audit2allow(1)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/8/semanage&quot;&gt;&lt;code&gt;semanage(8)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/8/html/using_selinux/writing-a-custom-selinux-policy_using-selinux&quot;&gt;Writing a custom SELinux policy (Red Hat docs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="bazzite"/><category term="selinux"/><category term="systemd"/><category term="rsync"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Escaping the AWS SSM AppArmor profile with systemd-run</title>
    <link href="https://daal.cloud/articles/ssm-shell-apparmor-systemd-run/"/>
    <id>https://daal.cloud/articles/ssm-shell-apparmor-systemd-run/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>On Ubuntu, SSM Session Manager shells inherit the snap-amazon-ssm-agent AppArmor profile. Even root hits silent EACCES on writes. Escape via systemd-run.</summary>
    <content type="html">&lt;p&gt;On Ubuntu via &lt;a href=&quot;https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html&quot;&gt;SSM Session Manager&lt;/a&gt;, your shell (and every child it spawns) runs under the &lt;code&gt;snap.amazon-ssm-agent.amazon-ssm-agent&lt;/code&gt; AppArmor profile. uid 0 with &lt;code&gt;CAP_DAC_OVERRIDE&lt;/code&gt; doesn&#39;t get out of it. Writes to paths like &lt;code&gt;/var/log/postgresql/*&lt;/code&gt; return &lt;code&gt;EACCES&lt;/code&gt; even with a clean &lt;code&gt;lsattr&lt;/code&gt;, a writable fs, and the right caps.&lt;/p&gt;
&lt;h2 id=&quot;confirm-it-s-apparmor&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#confirm-it-s-apparmor&quot;&gt;#&lt;/a&gt; Confirm it&#39;s AppArmor&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cat /proc/self/attr/current
snap.amazon-ssm-agent.amazon-ssm-agent (enforce)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dmesg&lt;/code&gt; will probably stay quiet. Snap profiles use &lt;code&gt;audit deny&lt;/code&gt; for &amp;quot;expected&amp;quot; paths, which suppresses the kernel audit line. When entries do show up they can lag a couple of minutes.&lt;/p&gt;
&lt;h2 id=&quot;escape-via-systemd-run&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#escape-via-systemd-run&quot;&gt;#&lt;/a&gt; Escape via systemd-run&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; runs as PID 1, outside any AppArmor profile. Anything it starts is unconfined. &lt;code&gt;systemd-run --pipe --wait&lt;/code&gt; delegates a command to PID 1 and pipes the result back:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemd-run --pipe --wait &amp;lt;cmd&amp;gt; [args...]
sudo systemd-run --pipe --wait --uid=postgres bash -c &#39;truncate -s 0 /var/log/postgresql/postgresql-16-main.log&#39;
sudo systemd-run --pipe --wait tee /etc/foo/bar.conf &amp;lt;&amp;lt;&#39;EOF&#39;
some content
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--pipe&lt;/code&gt; forwards stdin/stdout/stderr.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--wait&lt;/code&gt; is synchronous and propagates the exit code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--uid=&amp;lt;user&amp;gt;&lt;/code&gt; drops privilege. PID 1 can become any uid.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;better-alternatives&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#better-alternatives&quot;&gt;#&lt;/a&gt; Better alternatives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SSH with a real keypair.&lt;/strong&gt; SSH sessions don&#39;t inherit the snap profile. Worth the setup if you touch the box more than occasionally.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-bother&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-bother&quot;&gt;#&lt;/a&gt; Why bother&lt;/h2&gt;
&lt;p&gt;SSM is the right tool for ad-hoc ops without exposing SSH, or when you&#39;ve lost the original key. It&#39;s also audited, which is nice. Knowing about the AppArmor inheritance saves you the hour of wondering why root can&#39;t write to a directory it owns (like I did).&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/podman-vs-docker/&quot;&gt;Living without docker&amp;colon; podman as a daily driver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/mcp-env-vars-not-from-bashrc/&quot;&gt;Why your MCP server cannot see env vars from &amp;period;bashrc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ubuntu.com/server/docs/security-apparmor&quot;&gt;AppArmor in Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/systemd-run.html&quot;&gt;&lt;code&gt;systemd-run(1)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html&quot;&gt;AWS SSM Session Manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="aws"/><category term="ssm"/><category term="apparmor"/><category term="linux"/><category term="ubuntu"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Why your MCP server cannot see env vars from .bashrc</title>
    <link href="https://daal.cloud/articles/mcp-env-vars-not-from-bashrc/"/>
    <id>https://daal.cloud/articles/mcp-env-vars-not-from-bashrc/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Claude Code spawns MCP servers as plain child processes. They never source .bashrc. Set MCP env vars in environment.d or the MCP env config, not your shell rc files.</summary>
    <content type="html">&lt;p&gt;You add an &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;MCP&lt;/a&gt; server to &lt;a href=&quot;https://docs.claude.com/en/docs/claude-code/overview&quot;&gt;Claude Code&lt;/a&gt; and it requires an environment variable. But the server doesn&#39;t show up when you run &lt;code&gt;/mcp&lt;/code&gt;, no error surfaces, nothing useful in the logs. Nine times out of ten: an env var it needs is set in &lt;code&gt;~/.bashrc&lt;/code&gt;, and nothing Claude Code launches will ever see it.&lt;/p&gt;
&lt;h2 id=&quot;the-mechanic&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-mechanic&quot;&gt;#&lt;/a&gt; The mechanic&lt;/h2&gt;
&lt;p&gt;Claude Code spawns each MCP server as a plain child process. No interactive shell, no &lt;code&gt;.bashrc&lt;/code&gt; sourcing. &lt;code&gt;.bashrc&lt;/code&gt; is for interactive shells only. Non-interactive children inherit the parent&#39;s already-resolved environment, not the file.&lt;/p&gt;
&lt;p&gt;So this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ~/.bashrc
export MY_API_KEY=sk-xxxxxxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is invisible to MCP servers, hooks, and skill scripts. The server starts, can&#39;t read the var, exits silently. Claude Code only sees &amp;quot;didn&#39;t initialise.&amp;quot;&lt;/p&gt;
&lt;h2 id=&quot;where-to-put-env-vars-instead&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#where-to-put-env-vars-instead&quot;&gt;#&lt;/a&gt; Where to put env vars instead&lt;/h2&gt;
&lt;p&gt;In rough order of preference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;systemd &lt;code&gt;environment.d&lt;/code&gt;&lt;/strong&gt; (Linux, systemd-based distros). Drop &lt;code&gt;~/.config/environment.d/mcp.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MY_API_KEY=sk-xxxxxxxx
ANTHROPIC_LOG=debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are part of the user session, loaded by &lt;code&gt;systemd --user&lt;/code&gt;, inherited by every desktop, IDE, terminal, and Claude Code process spawned from that session. Log out / log in to apply, or &lt;code&gt;systemctl --user daemon-reload&lt;/code&gt; and relog.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The MCP server&#39;s own &lt;code&gt;env&lt;/code&gt; config.&lt;/strong&gt; In &lt;code&gt;~/.claude/settings.json&lt;/code&gt; or project &lt;code&gt;.mcp.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;myserver&amp;quot;: {
      &amp;quot;command&amp;quot;: &amp;quot;/path/to/server&amp;quot;,
      &amp;quot;env&amp;quot;: { &amp;quot;MY_API_KEY&amp;quot;: &amp;quot;sk-xxxxxxxx&amp;quot; }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explicit and per-server. Downside: secrets end up in a config file. Only safe if it isn&#39;t synced or committed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;~/.profile&lt;/code&gt; or &lt;code&gt;~/.zprofile&lt;/code&gt;&lt;/strong&gt;. Login shells source these, and most display managers do start the user session via a login shell. Cleaner than &lt;code&gt;.bashrc&lt;/code&gt; but more fragile across distros.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;launchctl setenv&lt;/code&gt;&lt;/strong&gt; on macOS. Persists only inside the current &lt;code&gt;launchd&lt;/code&gt; session unless you wire a &lt;code&gt;LaunchAgent&lt;/code&gt; plist.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;confirming-the-failure-mode&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#confirming-the-failure-mode&quot;&gt;#&lt;/a&gt; Confirming the failure mode&lt;/h2&gt;
&lt;p&gt;When an MCP server is missing from &lt;code&gt;/mcp&lt;/code&gt; and you suspect env vars are the cause:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Run the binary from a non-interactive shell to reproduce:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash -c &#39;/path/to/mcp-server&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An interactive shell sources &lt;code&gt;.bashrc&lt;/code&gt;. &lt;code&gt;bash -c&lt;/code&gt; doesn&#39;t. If it fails here, it&#39;ll fail when Claude Code spawns it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a startup log line that prints &lt;code&gt;os.environ.get(&amp;quot;MY_API_KEY&amp;quot;, &amp;quot;(missing)&amp;quot;)&lt;/code&gt;. Fastest way to confirm the var isn&#39;t reaching the child.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;child-processes-not-seeing-what-you-d-expect-is-a-recurring-failure-mode&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#child-processes-not-seeing-what-you-d-expect-is-a-recurring-failure-mode&quot;&gt;#&lt;/a&gt; Child processes not seeing what you&#39;d expect is a recurring failure mode&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/ssm-shell-apparmor-systemd-run/&quot;&gt;Escaping the AWS SSM AppArmor profile with systemd-run&lt;/a&gt;. SSM child shells inherit an AppArmor profile silently.&lt;/li&gt;
&lt;li&gt;Cron with no &lt;code&gt;$PATH&lt;/code&gt; because cron&#39;s environment is a stripped-down version of root&#39;s login env.&lt;/li&gt;
&lt;li&gt;Docker build steps not seeing &lt;code&gt;$DOCKER_BUILDKIT&lt;/code&gt; because the daemon, not your shell, holds it. (See &lt;a href=&quot;/articles/podman-vs-docker/&quot;&gt;Living without docker&amp;colon; podman as a daily driver&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In every case, don&#39;t assume your shell&#39;s environment is what the child process actually sees. Set the var where the child picks it up.&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/docs/specification/server&quot;&gt;Model Context Protocol — server configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/environment.d.html&quot;&gt;&lt;code&gt;environment.d(5)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.claude.com/en/docs/claude-code/mcp&quot;&gt;Claude Code MCP docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="claude"/><category term="mcp"/><category term="claude-code"/><category term="linux"/><category term="systemd"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>Pagefind: full-text search for static sites, no backend</title>
    <link href="https://daal.cloud/articles/pagefind-client-side-search/"/>
    <id>https://daal.cloud/articles/pagefind-client-side-search/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Pagefind builds a chunked search index at build time and queries it from the browser via WebAssembly. Zero servers, zero queries leaving the page, and it scales to thousands of pages without shipping the whole index up front.</summary>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind&lt;/a&gt; is a search library for static sites. It indexes the built HTML once at build time and serves the index as static files. Queries run in the visitor&#39;s browser against a small WASM binary - no search backend, no API key, no third-party JS.&lt;/p&gt;
&lt;p&gt;This site uses it for the &lt;code&gt;/articles/&lt;/code&gt; index.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#how-it-works&quot;&gt;#&lt;/a&gt; How it works&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build step.&lt;/strong&gt; After your site generator writes HTML, you run &lt;code&gt;pagefind --site _site&lt;/code&gt;. Pagefind crawls the output, looks for &lt;code&gt;data-pagefind-body&lt;/code&gt; (or just falls back to &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;), tokenises the text, and writes:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pagefind/pagefind.js&lt;/code&gt; - the loader.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pagefind/pagefind-ui.js&lt;/code&gt; + &lt;code&gt;pagefind-ui.css&lt;/code&gt; - the optional default UI (a search input + result list).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pagefind/wasm.*.pagefind&lt;/code&gt; - the &lt;a href=&quot;https://webassembly.org/&quot;&gt;WASM binary&lt;/a&gt; that does the actual querying.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pagefind/index/*.pagefind&lt;/code&gt; - the index, &lt;a href=&quot;https://pagefind.app/docs/architecture/&quot;&gt;chunked by term prefix&lt;/a&gt; (typically a few hundred bytes to a few KB per chunk).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pagefind/fragment/*.pagefind&lt;/code&gt; - per-page metadata (title, URL, excerpt source).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page load.&lt;/strong&gt; The browser loads &lt;code&gt;pagefind-ui.js&lt;/code&gt; lazily. Nothing else fetches yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;First keystroke.&lt;/strong&gt; Pagefind loads the WASM (~70KB gzip), then fetches only the index chunks matching the prefix the user typed. For a query like &lt;code&gt;podm&lt;/code&gt;, it pulls the &lt;code&gt;pod&lt;/code&gt; chunk, not the whole index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result render.&lt;/strong&gt; Matched fragment files are fetched to build excerpts. Each result is one or two extra ~500-byte requests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The total network cost for a typical search is well under 200KB even on a multi-thousand-page site, because nothing is loaded until the user actually searches and only the relevant chunks come down.&lt;/p&gt;
&lt;h2 id=&quot;why-it-beats-a-backend-search-service&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-beats-a-backend-search-service&quot;&gt;#&lt;/a&gt; Why it beats a backend search service&lt;/h2&gt;
&lt;p&gt;For a static site or a personal KB, Pagefind cuts out a bunch of operational headaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No server.&lt;/strong&gt; Nothing to provision, monitor, rate-limit, patch, or pay for. It&#39;s bytes on a CDN. CF Pages / GitHub Pages / S3 / Netlify all serve it the same way they serve the rest of your site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No third-party.&lt;/strong&gt; Algolia/Typesense/Elastic-as-a-service all add a domain to your CSP, a tracking surface, an SLA you depend on, and a key that can leak. Pagefind queries never leave the page - relevant if you write about anything you&#39;d rather not hand to a vendor&#39;s logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Private by design.&lt;/strong&gt; No analytics. No &amp;quot;what did this user search for&amp;quot; telemetry to leak. There&#39;s no place for it to go, since the &lt;a href=&quot;https://pagefind.app/docs/security/&quot;&gt;search runs entirely in the page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Builds in the same pipeline.&lt;/strong&gt; It&#39;s one extra command after your site generator. No separate index-rebuild job, no webhook, no eventual-consistency window between &amp;quot;I published a page&amp;quot; and &amp;quot;it&#39;s searchable.&amp;quot; When the site deploys, the index deploys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trivial to roll back.&lt;/strong&gt; A bad index ships with a bad deploy and reverts with a &lt;code&gt;git revert&lt;/code&gt; + redeploy. With a hosted index, rollback means re-indexing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cheap.&lt;/strong&gt; &lt;a href=&quot;https://github.com/CloudCannon/pagefind&quot;&gt;Free.&lt;/a&gt; MIT-licensed. The hosting cost is whatever your static host charges (often $0).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-a-backend-still-wins&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#where-a-backend-still-wins&quot;&gt;#&lt;/a&gt; Where a backend still wins&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tens of thousands of pages.&lt;/strong&gt; Pagefind chunks the index but the &lt;a href=&quot;https://pagefind.app/docs/indexing/&quot;&gt;fragment count grows linearly&lt;/a&gt;. Past ~10k pages, the build step gets slow and the cold-start WASM + chunk fetches start to feel sluggish on mobile data. Backend search keeps a constant client cost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-user authorisation.&lt;/strong&gt; If results depend on who&#39;s logged in (private docs, multi-tenant SaaS), the index can&#39;t be public, and shipping a per-user index isn&#39;t realistic. You need a server enforcing access at query time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frequent index updates without a deploy.&lt;/strong&gt; If content changes outside the build (user-generated, CMS-driven, real-time), you need an index that updates independently. Pagefind only updates when you rebuild.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fancy features.&lt;/strong&gt; Synonyms, typo-tolerance with a learned language model, query analytics, A/B-tested ranking, federated search across heterogeneous sources - all things &lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt; / &lt;a href=&quot;https://typesense.org/&quot;&gt;Typesense&lt;/a&gt; / &lt;a href=&quot;https://www.meilisearch.com/&quot;&gt;Meilisearch&lt;/a&gt; do well and Pagefind doesn&#39;t try to.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;integration-notes-eleventy-specifically&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#integration-notes-eleventy-specifically&quot;&gt;#&lt;/a&gt; Integration notes (Eleventy specifically)&lt;/h2&gt;
&lt;p&gt;Three things to remember:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pagefind has no watch mode.&lt;/strong&gt; &lt;code&gt;eleventy --serve&lt;/code&gt; keeps the build in memory and never invokes Pagefind. For dev, &lt;code&gt;npm run dev&lt;/code&gt; shows you the empty &lt;code&gt;#search&lt;/code&gt; div with no UI; it&#39;s not broken. To actually exercise search, build to disk and serve the directory: &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run serve&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indexing scope.&lt;/strong&gt; By default Pagefind indexes everything inside &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Wrap your real content in &lt;code&gt;&amp;lt;main data-pagefind-body&amp;gt;&lt;/code&gt; (or &lt;code&gt;&amp;lt;article data-pagefind-body&amp;gt;&lt;/code&gt;) to exclude nav/footer chrome from search results. Worth doing - otherwise every result excerpt starts with &amp;quot;home articles Dark mode ☾&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI customisation through CSS variables.&lt;/strong&gt; The default &lt;code&gt;PagefindUI&lt;/code&gt; widget exposes &lt;code&gt;--pagefind-ui-primary&lt;/code&gt;, &lt;code&gt;--pagefind-ui-background&lt;/code&gt;, &lt;code&gt;--pagefind-ui-border&lt;/code&gt;, etc. You set them on &lt;code&gt;:root&lt;/code&gt; and the widget picks them up. The Pagefind override block at the bottom of &lt;code&gt;css/style.css&lt;/code&gt; is all this site does to theme it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/mcp-env-vars-not-from-bashrc/&quot;&gt;Why your MCP server cannot see env vars from &amp;period;bashrc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagefind.app/docs/architecture/&quot;&gt;Pagefind architecture overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/CloudCannon/pagefind&quot;&gt;Pagefind on GitHub (CloudCannon)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="pagefind"/><category term="search"/><category term="static-sites"/><category term="eleventy"/><category term="devtools"/>
  </entry>
  
  <entry>
    <title>Living without docker: podman as a daily driver</title>
    <link href="https://daal.cloud/articles/podman-vs-docker/"/>
    <id>https://daal.cloud/articles/podman-vs-docker/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Podman is a near drop-in replacement for the Docker CLI. Daemonless, rootless by default, ships in immutable distros. Muscle-memory translation and the few places the abstraction leaks.</summary>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt; ships on most modern Linux desktops, &lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt; included. The CLI is intentionally &lt;code&gt;docker&lt;/code&gt;-compatible (most subcommands and flags work identically), but two things differ under the hood:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No daemon.&lt;/strong&gt; Podman invokes containers directly via &lt;code&gt;runc&lt;/code&gt;/&lt;code&gt;crun&lt;/code&gt;. There&#39;s no long-running root service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rootless by default.&lt;/strong&gt; Each user has their own container store under &lt;code&gt;~/.local/share/containers/&lt;/code&gt;. Containers run mapped to your UID via &lt;a href=&quot;https://docs.kernel.org/admin-guide/namespaces/index.html&quot;&gt;user namespaces&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both are a security win. They also remove the &amp;quot;is the daemon running?&amp;quot; and &amp;quot;is my user in the &lt;code&gt;docker&lt;/code&gt; group?&amp;quot; failure modes.&lt;/p&gt;
&lt;h2 id=&quot;common-docker-commands-as-podman-commands&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#common-docker-commands-as-podman-commands&quot;&gt;#&lt;/a&gt; Common docker commands as podman commands&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker pull alpine          →   podman pull alpine
docker run -it alpine sh    →   podman run -it alpine sh
docker build -t foo .       →   podman build -t foo .
docker compose up           →   podman compose up        # via podman-compose
docker ps                   →   podman ps
docker exec -it &amp;lt;id&amp;gt; sh     →   podman exec -it &amp;lt;id&amp;gt; sh
docker logs -f &amp;lt;id&amp;gt;         →   podman logs -f &amp;lt;id&amp;gt;
docker network ls           →   podman network ls
docker volume create x      →   podman volume create x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your fingers keep typing &lt;code&gt;docker&lt;/code&gt;, install &lt;a href=&quot;https://packages.fedoraproject.org/pkgs/podman/podman-docker/&quot;&gt;&lt;code&gt;podman-docker&lt;/code&gt;&lt;/a&gt;. It symlinks &lt;code&gt;/usr/bin/docker&lt;/code&gt; to &lt;code&gt;/usr/bin/podman&lt;/code&gt; so old scripts keep working. Bazzite ships it by default.&lt;/p&gt;
&lt;h2 id=&quot;where-the-abstraction-leaks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#where-the-abstraction-leaks&quot;&gt;#&lt;/a&gt; Where the abstraction leaks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;docker compose&lt;/code&gt; v2 vs &lt;code&gt;podman-compose&lt;/code&gt;.&lt;/strong&gt; Parity is close but not perfect. The &lt;a href=&quot;https://github.com/containers/podman-compose&quot;&gt;compose plugin shipped with Podman 5.x&lt;/a&gt; handles most multi-container apps. If you hit a wall, &lt;a href=&quot;https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html&quot;&gt;Quadlet&lt;/a&gt; units are closer to how production actually runs anyway.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker socket.&lt;/strong&gt; Tools that hardcode &lt;code&gt;/var/run/docker.sock&lt;/code&gt; need a podman socket. Run &lt;code&gt;systemctl --user enable --now podman.socket&lt;/code&gt;, then point them at &lt;code&gt;DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NVIDIA passthrough.&lt;/strong&gt; &lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html&quot;&gt;The container toolkit&lt;/a&gt; supports both runtimes. The podman path needs &lt;code&gt;--security-opt=label=disable&lt;/code&gt; on SELinux distros.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buildkit features.&lt;/strong&gt; &lt;code&gt;docker build --secret&lt;/code&gt;, &lt;code&gt;--ssh&lt;/code&gt;, &lt;code&gt;--mount=type=cache&lt;/code&gt; all have podman equivalents. Flag names differ slightly. Check &lt;code&gt;podman build --help&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;macOS / Windows.&lt;/strong&gt; Podman runs a Linux VM under the hood (&lt;code&gt;podman machine&lt;/code&gt;). Resource limits and bind mounts work, but &lt;code&gt;host.docker.internal&lt;/code&gt; becomes &lt;code&gt;host.containers.internal&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-it-matters-on-immutable-distros&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-matters-on-immutable-distros&quot;&gt;#&lt;/a&gt; Why it matters on immutable distros&lt;/h2&gt;
&lt;p&gt;On &lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt; and other rpm-ostree systems, you avoid layering RPMs into the base image (see &lt;a href=&quot;/articles/rpm-ostree-rebase-cheatsheet/&quot;&gt;rpm-ostree&amp;colon; rebase&amp;comma; pin&amp;comma; rollback&lt;/a&gt;). Containers are how you install traditional CLI tooling. &lt;code&gt;distrobox&lt;/code&gt; and &lt;code&gt;toolbx&lt;/code&gt; both wrap Podman to give you a transparent shell into a container with your home directory mounted in. That&#39;s where &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;terraform&lt;/code&gt;, etc. should live.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/rpm-ostree-rebase-cheatsheet/&quot;&gt;rpm-ostree&amp;colon; rebase&amp;comma; pin&amp;comma; rollback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.podman.io/&quot;&gt;Podman documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="containers"/><category term="podman"/><category term="docker"/><category term="linux"/><category term="devtools"/><category term="bazzite"/>
  </entry>
  
  <entry>
    <title>Bazzite: an immutable gaming-first Fedora variant</title>
    <link href="https://daal.cloud/articles/bazzite-overview/"/>
    <id>https://daal.cloud/articles/bazzite-overview/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Short orientation to Bazzite. What it is, why the immutable layout matters, how it changes day-to-day system management.</summary>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://bazzite.gg/&quot;&gt;Bazzite&lt;/a&gt; is a community-built Linux distribution derived from Fedora&#39;s atomic desktop family (&lt;a href=&quot;https://fedoraproject.org/atomic-desktops/silverblue/&quot;&gt;Silverblue&lt;/a&gt;, &lt;a href=&quot;https://fedoraproject.org/atomic-desktops/kinoite/&quot;&gt;Kinoite&lt;/a&gt;) with a heavy focus on gaming, handhelds (Steam Deck, ROG Ally), and home-theatre boxes. Ships with the Steam stack, &lt;a href=&quot;https://www.mesa3d.org/&quot;&gt;Mesa&lt;/a&gt; drivers, Proton-tuned kernels, and &lt;code&gt;gamescope&lt;/code&gt; preconfigured.&lt;/p&gt;
&lt;h2 id=&quot;mental-model&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#mental-model&quot;&gt;#&lt;/a&gt; Mental model&lt;/h2&gt;
&lt;p&gt;Two things change relative to a traditional Fedora install:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The base system is image-based and read-only.&lt;/strong&gt; Updates are atomic. You boot into a new commit or roll back to the previous one. There&#39;s no &lt;code&gt;dnf install&lt;/code&gt; of arbitrary RPMs into the running system. The OS image is built upstream and you rebase between images.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User software lives in containers and Flatpaks.&lt;/strong&gt; &lt;a href=&quot;https://flatpak.org/&quot;&gt;Flatpak&lt;/a&gt; for graphical apps, &lt;a href=&quot;https://distrobox.it/&quot;&gt;&lt;code&gt;distrobox&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://containertoolbx.org/&quot;&gt;&lt;code&gt;toolbx&lt;/code&gt;&lt;/a&gt; for traditional CLI tooling, &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; for one-shot CLI packages.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That split is why Bazzite is hard to brick (you can break the user side without touching boot).&lt;/p&gt;
&lt;h2 id=&quot;day-to-day&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#day-to-day&quot;&gt;#&lt;/a&gt; Day-to-day&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update.&lt;/strong&gt; &lt;code&gt;rpm-ostree upgrade&lt;/code&gt;, or &lt;code&gt;ujust update&lt;/code&gt; on Bazzite (which wraps the same machinery and also refreshes Flatpaks). Reboot to apply. See &lt;a href=&quot;/articles/rpm-ostree-rebase-cheatsheet/&quot;&gt;rpm-ostree&amp;colon; rebase&amp;comma; pin&amp;comma; rollback&lt;/a&gt; for rebase, pin, and rollback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install a GUI app.&lt;/strong&gt; Prefer Flatpak: &lt;code&gt;flatpak install flathub &amp;lt;app&amp;gt;&lt;/code&gt;. Desktop integration is fine for almost everything. Notable exception is browser → 1Password. See &lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt; for some notes on that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install a CLI tool.&lt;/strong&gt; Spin up a &lt;code&gt;toolbox&lt;/code&gt; or &lt;code&gt;distrobox&lt;/code&gt; container running Fedora or Arch, install the tool inside, export the binary. Containers are throwaway. Destroy and recreate when you want a clean state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container runtime.&lt;/strong&gt; Bazzite ships &lt;a href=&quot;https://podman.io/&quot;&gt;Podman&lt;/a&gt;, not Docker. See also &lt;a href=&quot;/articles/podman-vs-docker/&quot;&gt;Living without docker&amp;colon; podman as a daily driver&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-troubleshooting&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-troubleshooting&quot;&gt;#&lt;/a&gt; When troubleshooting&lt;/h2&gt;
&lt;p&gt;&amp;quot;Where does this thing live?&amp;quot; is harder on Bazzite than on stock Fedora. A given binary is in one of four places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image layer.&lt;/strong&gt; Read-only at &lt;code&gt;/usr&lt;/code&gt;. Changes only via &lt;code&gt;rpm-ostree&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layered RPM.&lt;/strong&gt; Added via &lt;code&gt;rpm-ostree install&lt;/code&gt;. Listed by &lt;code&gt;rpm-ostree status -v&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flatpak.&lt;/strong&gt; Sandboxed under &lt;code&gt;/var/lib/flatpak&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container.&lt;/strong&gt; Inside &lt;code&gt;distrobox&lt;/code&gt; / &lt;code&gt;toolbox&lt;/code&gt;. Invisible to the host package manager.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Knowing which layer a misbehaving binary lives in tells you which tool fixes it. A 1Password browser plugin failing to talk to the desktop app is almost always a Flatpak permission / host-bridge problem, not a system one.&lt;/p&gt;
&lt;h2 id=&quot;privacy-footnote&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#privacy-footnote&quot;&gt;#&lt;/a&gt; Privacy footnote&lt;/h2&gt;
&lt;p&gt;Everything in this note is public Bazzite behaviour, nothing specific to my install. For the authoritative reference, &lt;code&gt;man rpm-ostree&lt;/code&gt; and the &lt;a href=&quot;https://docs.bazzite.gg/&quot;&gt;Bazzite docs&lt;/a&gt;.&lt;/p&gt;
</content>
    <category term="linux"/><category term="bazzite"/><category term="immutable-os"/><category term="gaming"/><category term="fedora"/>
  </entry>
  
  <entry>
    <title>Making 1Password browser extensions talk to the Flatpak desktop app</title>
    <link href="https://daal.cloud/articles/flatpak-1password-browser-bridge/"/>
    <id>https://daal.cloud/articles/flatpak-1password-browser-bridge/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Browser extension can&#39;t see a Flatpak 1Password desktop app via native messaging. Sandbox isolation is the cause. Fix is the explicit cross-sandbox bridge 1Password ships.</summary>
    <content type="html">&lt;p&gt;If you install the &lt;a href=&quot;https://1password.com/downloads/linux/&quot;&gt;1Password desktop app&lt;/a&gt; as a Flatpak (the recommended path on &lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt; and other immutable distros), and your browser is also a Flatpak, &amp;quot;Connect to 1Password&amp;quot; hangs. The unlock screen sits forever, or the extension claims the desktop app isn&#39;t running even though it clearly is.&lt;/p&gt;
&lt;p&gt;Sandboxing problem, not a 1Password bug.&lt;/p&gt;
&lt;h2 id=&quot;why-it-breaks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-it-breaks&quot;&gt;#&lt;/a&gt; Why it breaks&lt;/h2&gt;
&lt;p&gt;The desktop app and the extension talk via &lt;a href=&quot;https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging&quot;&gt;native messaging&lt;/a&gt;. The browser launches a small helper binary (&lt;code&gt;com.1password.1password.json&lt;/code&gt; on Linux) that opens a Unix socket inside the desktop app&#39;s runtime directory. Normal install: both sides share &lt;code&gt;/run/user/&amp;lt;uid&amp;gt;/&lt;/code&gt;, they find each other.&lt;/p&gt;
&lt;p&gt;Under Flatpak, each app runs in its own sandbox. The browser sandbox can&#39;t see 1Password&#39;s &lt;code&gt;/run/user/&amp;lt;uid&amp;gt;/&lt;/code&gt; by default. Even if it could, the &lt;code&gt;NativeMessagingHosts&lt;/code&gt; manifest path the browser scans (&lt;code&gt;~/.config/&amp;lt;browser&amp;gt;/NativeMessagingHosts/&lt;/code&gt;) is rewritten by the sandbox to a Flatpak-private location.&lt;/p&gt;
&lt;p&gt;Net result: browser never sees the manifest, never spawns the helper, never finds the socket.&lt;/p&gt;
&lt;h2 id=&quot;fix&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#fix&quot;&gt;#&lt;/a&gt; Fix&lt;/h2&gt;
&lt;p&gt;1Password ships a cross-sandbox bridge. Set it up once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Make the manifest visible to Flatpak browsers
mkdir -p ~/.var/app/&amp;lt;browser-app-id&amp;gt;/config/&amp;lt;browser&amp;gt;/NativeMessagingHosts
flatpak run --command=/app/bin/1password-cli com.1password.1Password --setup-browser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Typical setup (Vivaldi or Chromium as Flatpak, 1Password as Flatpak):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Allow the 1Password app to talk to host browsers
flatpak override --user --talk-name=com.1password.1Password com.vivaldi.Vivaldi
flatpak override --user --talk-name=com.1password.1Password org.chromium.Chromium
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then enable &lt;strong&gt;Browser Integration&lt;/strong&gt; inside the desktop app: &lt;em&gt;Settings → Developer → Allow connection from browser extensions&lt;/em&gt;. The integration check inside the extension&#39;s settings should now flip to green.&lt;/p&gt;
&lt;h2 id=&quot;when-it-still-doesn-t-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-it-still-doesn-t-work&quot;&gt;#&lt;/a&gt; When it still doesn&#39;t work&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wayland-only browsers.&lt;/strong&gt; A few extensions misbehave under pure Wayland. &lt;code&gt;--ozone-platform-hint=auto&lt;/code&gt; in the browser launch flags often fixes it. For Vivaldi specifically, &lt;a href=&quot;/articles/vivaldi-persistent-flags/&quot;&gt;Persistent CLI flags for Vivaldi on Linux&lt;/a&gt; is where the flag belongs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App ID mismatch.&lt;/strong&gt; 1Password 8 uses &lt;code&gt;com.1password.1Password&lt;/code&gt;. The 7.x snap manifest used &lt;code&gt;com.onepassword.OnePassword&lt;/code&gt;. Mixing the two silently fails. Check &lt;code&gt;flatpak list | grep -i password&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;com.1password.1Password.Policy&lt;/code&gt; missing.&lt;/strong&gt; Happens if you grabbed the app from a third-party Flathub mirror without the &lt;code&gt;Policy&lt;/code&gt; extension. Reinstall from the official Flathub remote.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PIN/biometrics on the wrong account.&lt;/strong&gt; Only one signed-in account at a time can serve native messaging. Switch primary account in &lt;em&gt;Settings → Accounts&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Why Flatpak is the default install path on immutable Fedora.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/rpm-ostree-rebase-cheatsheet/&quot;&gt;rpm-ostree&amp;colon; rebase&amp;comma; pin&amp;comma; rollback&lt;/a&gt;. If reinstalling 1Password meant a base image change.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.1password.com/docs/cli/native-messaging/&quot;&gt;1Password — native messaging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.flatpak.org/en/latest/sandbox-permissions.html&quot;&gt;Flatpak sandbox permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="flatpak"/><category term="1password"/><category term="bazzite"/><category term="browsers"/><category term="troubleshooting"/>
  </entry>
  
  <entry>
    <title>rpm-ostree: rebase, pin, rollback</title>
    <link href="https://daal.cloud/articles/rpm-ostree-rebase-cheatsheet/"/>
    <id>https://daal.cloud/articles/rpm-ostree-rebase-cheatsheet/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Cheat-sheet for moving between OS images on rpm-ostree systems. What each verb actually does and how not to lose access to a working boot.</summary>
    <content type="html">&lt;p&gt;&lt;code&gt;rpm-ostree&lt;/code&gt; manages the OS image on Fedora&#39;s atomic desktops (&lt;a href=&quot;https://fedoraproject.org/atomic-desktops/silverblue/&quot;&gt;Silverblue&lt;/a&gt;, &lt;a href=&quot;https://fedoraproject.org/atomic-desktops/kinoite/&quot;&gt;Kinoite&lt;/a&gt;, &lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt;). A handful of verbs cover 90% of what you need.&lt;/p&gt;
&lt;h2 id=&quot;the-shape&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#the-shape&quot;&gt;#&lt;/a&gt; The shape&lt;/h2&gt;
&lt;p&gt;You always have &lt;strong&gt;two deployments&lt;/strong&gt; at minimum: the one you&#39;re running and the one you&#39;ll roll back to. Each &lt;code&gt;rpm-ostree&lt;/code&gt; operation stages a &lt;em&gt;new&lt;/em&gt; deployment. Nothing changes until you reboot.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ rpm-ostree status -v
State: idle
Deployments:
* ostree-image-signed:docker://ghcr.io/ublue-os/bazzite:stable
                   Digest: sha256:…
                  Version: 41.20260420.0   (current)
  ostree-image-signed:docker://ghcr.io/ublue-os/bazzite:stable
                   Digest: sha256:…
                  Version: 41.20260413.0   (rollback target)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;*&lt;/code&gt; marks &amp;quot;currently booted&amp;quot;. Reboot into the older deployment from GRUB to roll back.&lt;/p&gt;
&lt;h2 id=&quot;verbs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#verbs&quot;&gt;#&lt;/a&gt; Verbs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rpm-ostree upgrade&lt;/code&gt;. Fetch the latest ref of the same image. Reboot to apply.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rpm-ostree rollback&lt;/code&gt;. Make the previous deployment the default for next boot.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rpm-ostree rebase ostree-image-signed:docker://ghcr.io/&amp;lt;org&amp;gt;/&amp;lt;image&amp;gt;:&amp;lt;tag&amp;gt;&lt;/code&gt;. Switch to a different OS image. How you go between Bazzite GNOME and Bazzite KDE, between &lt;code&gt;:stable&lt;/code&gt; and &lt;code&gt;:testing&lt;/code&gt;, or to a &lt;a href=&quot;https://universal-blue.org/&quot;&gt;uBlue&lt;/a&gt; variant.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rpm-ostree deploy &amp;lt;version&amp;gt;&lt;/code&gt;. Pin to a specific image version. Subsequent &lt;code&gt;upgrade&lt;/code&gt; calls won&#39;t move past it until you &lt;code&gt;deploy --reset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rpm-ostree reset&lt;/code&gt;. Drop all layered packages and overrides. Useful when an upgrade is wedged because of a layering conflict.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pin-a-known-good-deployment&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#pin-a-known-good-deployment&quot;&gt;#&lt;/a&gt; Pin a known-good deployment&lt;/h2&gt;
&lt;p&gt;Pin the current deployment so a future rollback can reach it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ostree admin pin 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pinned deployments aren&#39;t garbage-collected when newer ones land. List with &lt;code&gt;ostree admin status&lt;/code&gt;. Unpin via &lt;code&gt;ostree admin pin --unpin &amp;lt;index&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;layering-escape-hatches&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#layering-escape-hatches&quot;&gt;#&lt;/a&gt; Layering escape hatches&lt;/h2&gt;
&lt;p&gt;You can install RPMs into the image with &lt;code&gt;rpm-ostree install &amp;lt;pkg&amp;gt;&lt;/code&gt;. They become &lt;strong&gt;layers&lt;/strong&gt; on top of the base. Keep this list short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every layer has to be re-applied on each rebase, slowing rebase and upgrade.&lt;/li&gt;
&lt;li&gt;A package that conflicts with the new base wedges the upgrade until you &lt;code&gt;rpm-ostree override remove&lt;/code&gt; or &lt;code&gt;rpm-ostree reset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Layered drivers and kernel modules are particularly fragile across major version jumps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-a-boot-fails&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-a-boot-fails&quot;&gt;#&lt;/a&gt; When a boot fails&lt;/h2&gt;
&lt;p&gt;GRUB shows the previous deployment as a separate entry. Reboot, pick one. The next &lt;code&gt;rpm-ostree status&lt;/code&gt; will show the broken one as &amp;quot;Deployment 1&amp;quot;. &lt;code&gt;rpm-ostree rollback&lt;/code&gt; makes it the default; &lt;code&gt;rpm-ostree cleanup -p&lt;/code&gt; drops it once you&#39;ve recovered.&lt;/p&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Context for why this is even a thing.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt;. Flatpak troubleshooting for sandbox-aware apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://coreos.github.io/rpm-ostree/&quot;&gt;rpm-ostree user manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.fedoraproject.org/en-US/fedora-silverblue/updates/&quot;&gt;Fedora image-based updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="bazzite"/><category term="immutable-os"/><category term="rpm-ostree"/><category term="fedora"/>
  </entry>
  
  <entry>
    <title>Persistent CLI flags for Vivaldi on Linux</title>
    <link href="https://daal.cloud/articles/vivaldi-persistent-flags/"/>
    <id>https://daal.cloud/articles/vivaldi-persistent-flags/</id>
    <published>2026-04-25T00:00:00.000Z</published>
    <updated>2026-04-25T00:00:00.000Z</updated>
    <summary>Vivaldi&#39;s launcher reads extra command-line flags from a config file in your home directory on every launch. That&#39;s where `--disable-gpu-compositing` and friends belong. Don&#39;t edit the .desktop file.</summary>
    <content type="html">&lt;p&gt;Vivaldi&#39;s launcher script (&lt;code&gt;/opt/vivaldi/vivaldi&lt;/code&gt;, with &lt;code&gt;/usr/bin/vivaldi-stable&lt;/code&gt; symlinked to it) reads extra CLI flags from a config file in &lt;code&gt;~/.config/&lt;/code&gt; on every launch. That&#39;s the right place for &lt;code&gt;--disable-gpu-compositing&lt;/code&gt;, &lt;code&gt;--enable-features=...&lt;/code&gt;, &lt;code&gt;--ozone-platform-hint=auto&lt;/code&gt;, and other flags you want Vivaldi to use.&lt;/p&gt;
&lt;h2 id=&quot;where-to-write-it&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#where-to-write-it&quot;&gt;#&lt;/a&gt; Where to write it&lt;/h2&gt;
&lt;p&gt;Config filename comes from the channel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stable:&lt;/strong&gt; &lt;code&gt;~/.config/vivaldi-stable.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Snapshot:&lt;/strong&gt; &lt;code&gt;~/.config/vivaldi-snapshot.conf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One flag per line. (&lt;code&gt;#&lt;/code&gt; is a comment). Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/vivaldi-stable.conf
--disable-gpu-compositing
--ozone-platform-hint=auto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quit and relaunch Vivaldi for changes to take effect. Closing the last window doesn&#39;t quit Vivaldi if you have a tray icon enabled.&lt;/p&gt;
&lt;h2 id=&quot;confirm-the-flags-are-active&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#confirm-the-flags-are-active&quot;&gt;#&lt;/a&gt; Confirm the flags are active&lt;/h2&gt;
&lt;p&gt;Open &lt;code&gt;vivaldi://version&lt;/code&gt; and check the &lt;strong&gt;Command Line&lt;/strong&gt; field. Every flag you set should be visible there alongside the defaults. Missing flag usually means a typo or a syntax issue (forgetting the leading &lt;code&gt;--&lt;/code&gt; is a common one).&lt;/p&gt;
&lt;h2 id=&quot;why-not-the-desktop-file&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#why-not-the-desktop-file&quot;&gt;#&lt;/a&gt; Why not the .desktop file&lt;/h2&gt;
&lt;p&gt;It&#39;s tempting to edit &lt;code&gt;/usr/share/applications/vivaldi-stable.desktop&lt;/code&gt; and append to &lt;code&gt;Exec=&lt;/code&gt;. Don&#39;t:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Package manager rewrites that file on every Vivaldi update. Your changes drop silently.&lt;/li&gt;
&lt;li&gt;Only affects launches from the desktop application menu, not the CLI or other launchers.&lt;/li&gt;
&lt;li&gt;The launcher reads the conf file &lt;em&gt;in addition to&lt;/em&gt; &lt;code&gt;Exec=&lt;/code&gt;, so you&#39;d still need the conf file for command-line launches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you genuinely need a per-application override (different flags for a launcher icon vs. a CLI alias), copy the .desktop file to &lt;code&gt;~/.local/share/applications/&lt;/code&gt; first and edit the copy.&lt;/p&gt;
&lt;h2 id=&quot;when-something-breaks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#when-something-breaks&quot;&gt;#&lt;/a&gt; When something breaks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPU glitches on Wayland.&lt;/strong&gt; &lt;code&gt;--disable-gpu-compositing&lt;/code&gt; usually fixes things, but expect higher CPU load since the CPU now handles compositing. &lt;code&gt;--ozone-platform-hint=auto&lt;/code&gt; can also help.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;vivaldi://flags&lt;/code&gt; overrides.&lt;/strong&gt; Those persist in the user profile, separate from CLI flags. If a flag from &lt;code&gt;~/.config/vivaldi-stable.conf&lt;/code&gt; doesn&#39;t seem to take effect, check &lt;code&gt;vivaldi://flags&lt;/code&gt; for a conflicting override.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vivaldi as Flatpak.&lt;/strong&gt; Conf file path is sandboxed to &lt;code&gt;~/.var/app/com.vivaldi.Vivaldi/config/vivaldi-stable.conf&lt;/code&gt;. Same syntax. Useful when Vivaldi is installed via Flatpak (e.g. on &lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#related&quot;&gt;#&lt;/a&gt; Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/articles/bazzite-overview/&quot;&gt;Bazzite&amp;colon; an immutable gaming-first Fedora variant&lt;/a&gt;. Flatpak browsers on immutable Fedora.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/articles/flatpak-1password-browser-bridge/&quot;&gt;Making 1Password browser extensions talk to the Flatpak desktop app&lt;/a&gt;. When the password manager extension can&#39;t talk to the desktop app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#references&quot;&gt;#&lt;/a&gt; References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://help.vivaldi.com/desktop/install-update/command-line-switches/&quot;&gt;Vivaldi help: command-line switches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;Chromium command-line switches list&lt;/a&gt;. Vivaldi accepts all of these.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <category term="linux"/><category term="vivaldi"/><category term="browsers"/><category term="desktop"/><category term="troubleshooting"/>
  </entry>
  
</feed>
