<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://www.yellowduck.be/pretty-atom-feed-v3.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link rel="alternate" href="https://www.yellowduck.be"/>
  <link rel="self" href="https://www.yellowduck.be/posts/feed"/>
  <author>
    <name>Pieter Claerhout</name>
    <email>pieter@yellowduck.be</email>
  </author>
  <id>https://www.yellowduck.be/posts/feed</id>
  <title>🐥 YellowDuck.be</title>
  <updated>2026-04-04T13:00:00Z</updated>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/designing-ai-features-that-actually-help-users"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Many AI features often misfire due to poor interface design rather than flawed models. By maintaining the original workflow structure, AI can effectively translate user intent into structured data that enhances functionality without overwhelming users.&lt;/p&gt;
&lt;p&gt;Traditional filtering interfaces can become complex and unwieldy, making it hard for users to articulate their needs. This approach proposes an intuitive AI-driven intent layer that simplifies user interactions by converting natural language into understandable filter parameters, leading to a more efficient and user-friendly experience.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.mimiquate.com/blog/designing-ai-features-that-actually-help-users&quot;&gt;Continue reading on &lt;strong&gt;www.mimiquate.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-04T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/designing-ai-features-that-actually-help-users</id>
    <title>🔗 Designing AI features that actually help users</title>
    <updated>2026-04-04T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/using-clickhouse-as-a-webhook-endpoint-with-hmac-verification"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;One of the standout features in ClickHouse 25.12 is the &lt;code&gt;HMAC&lt;/code&gt; function, which allows for message authentication with a shared key. This functionality enhances ClickHouse&apos;s ability to act as a webhook endpoint by enabling verification of incoming requests, thereby filtering out unauthorized sources.&lt;/p&gt;
&lt;p&gt;The setup involves three primary components: a staging table initiated with the &lt;code&gt;MergeTree&lt;/code&gt; engine, a materialized view for data validation, and a logs table to store successful entries. This method, paired with ClickHouse&apos;s architecture, ensures that only validated data is recorded, while invalid requests are rejected based on signature mismatches.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://clickhouse.com/blog/clickhouse-webhook-endpoint&quot;&gt;Continue reading on &lt;strong&gt;clickhouse.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/http&quot;&gt;#http&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-04T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/using-clickhouse-as-a-webhook-endpoint-with-hmac-verification</id>
    <title>🔗 Using ClickHouse as a webhook endpoint with HMAC verification</title>
    <updated>2026-04-04T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/re-implementing-the-google-reader-api-in-2025"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;In 2025, a post details the implementation of a Google Reader API-compatible service using Elixir. Despite challenges with the original API&apos;s lack of documentation, a baseline features implementation emerges, including logging in, fetching subscriptions, and managing feed item statuses.&lt;/p&gt;
&lt;p&gt;The project illustrates the complexity of working with reverse-engineered specifications and feeds, emphasizing the need for compatibility with evolving RSS clients. It describes essential concepts and endpoints while acknowledging that the API&apos;s intricacies make achieving a complete implementation challenging yet feasible for practical use.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.davd.io/posts/2025-02-05-reimplementing-google-reader-api-in-2025/&quot;&gt;Continue reading on &lt;strong&gt;www.davd.io&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-03T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/re-implementing-the-google-reader-api-in-2025</id>
    <title>🔗 Re-Implementing the Google Reader API in 2025</title>
    <updated>2026-04-03T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/thinking-elixir-podcast-297-javascript-joins-the-beam"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;News includes Quickbeam bringing a full JS runtime into the BEAM, Elixir’s type system inspiring Python’s Ruff, LiveView Debugger v0.7, Oban v2.21, and more!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://podcast.thinkingelixir.com/297&quot;&gt;Continue reading on &lt;strong&gt;podcast.thinkingelixir.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-03T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/thinking-elixir-podcast-297-javascript-joins-the-beam</id>
    <title>🔗 Thinking Elixir Podcast 297: JavaScript Joins the BEAM?</title>
    <updated>2026-04-03T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/ai-should-help-us-produce-better-code"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Many developers fear that AI tools will compromise code quality, producing hastily written, flawed code. Instead of accepting poor output, teams should address flaws directly and choose to improve their code quality.&lt;/p&gt;
&lt;p&gt;Technical debt often arises from trade-offs during development, and avoiding it in the first place is ideal. Coding agents, like Gemini Jules and OpenAI Codex, can effectively handle refactoring tasks and enhance the development process without disruption, ensuring higher quality outputs with minimal cost.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://simonwillison.net/guides/agentic-engineering-patterns/better-code/&quot;&gt;Continue reading on &lt;strong&gt;simonwillison.net&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-03T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/ai-should-help-us-produce-better-code</id>
    <title>🔗 AI should help us produce better code</title>
    <updated>2026-04-03T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/installing-claude-code-on-macos-with-homebrew-and-getting-the-latest-version"/>
    <content type="html">&lt;p&gt;If you’re installing Claude Code on macOS using Homebrew, the official instruction currently suggests:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;claude-code&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this works, it installs the &lt;strong&gt;latest stable version&lt;/strong&gt;, not the &lt;strong&gt;latest available version&lt;/strong&gt;. Depending on your use case, that can leave you behind on features and fixes.&lt;/p&gt;
&lt;h1&gt;Install the latest version&lt;/h1&gt;
&lt;p&gt;To install the most recent release, you should explicitly use:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;claude-code@latest&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures you’re running the newest version instead of the lagging stable cask.&lt;/p&gt;
&lt;h1&gt;Fix an existing installation&lt;/h1&gt;
&lt;p&gt;If you already installed &lt;code&gt;claude-code&lt;/code&gt; using the default command, you’ll need to replace it:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;uninstall&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;claude-code&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;claude-code@latest&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That removes the stable version and installs the latest one cleanly.&lt;/p&gt;
&lt;h1&gt;Why this matters&lt;/h1&gt;
&lt;p&gt;Homebrew distinguishes between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stable casks&lt;/strong&gt; → default installs (&lt;code&gt;claude-code&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioned or alternative casks&lt;/strong&gt; → explicit installs (&lt;code&gt;claude-code@latest&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, the naming is a bit misleading because &lt;code&gt;@latest&lt;/code&gt; is not the default. This has already caused confusion in the community and is being discussed upstream.&lt;/p&gt;
&lt;h1&gt;More context&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Issue discussion: &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/42176&quot;&gt;https://github.com/anthropics/claude-code/issues/42176&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Homebrew PR: &lt;a href=&quot;https://github.com/Homebrew/homebrew-cask/pull/255221&quot;&gt;https://github.com/Homebrew/homebrew-cask/pull/255221&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Takeaway&lt;/h1&gt;
&lt;p&gt;If you want the newest Claude Code features and fixes, don’t rely on the default install. Use:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;claude-code@latest&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you’ll avoid running an outdated version without realizing it.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/announcement&quot;&gt;#announcement&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/mac&quot;&gt;#mac&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-02T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/installing-claude-code-on-macos-with-homebrew-and-getting-the-latest-version</id>
    <title>🐥 Installing Claude Code on macOS with Homebrew (and getting the latest version)</title>
    <updated>2026-04-02T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/why-use-static-closures"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;In PHP, static closures are significant since they prevent unintended references to the object, enhancing memory management. By using the &lt;code&gt;static&lt;/code&gt; keyword, developers can ensure closures do not bind to &lt;code&gt;$this&lt;/code&gt;, allowing for earlier memory release and avoidance of memory leaks.&lt;/p&gt;
&lt;p&gt;The upcoming PHP 8.6 update aims to automate this process with inferred static closures, optimizing performance and memory usage. This change will streamline code, making it safer by default and reducing the risk of accidental captures, further promoting efficient memory management in PHP development.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://f2r.github.io/en/static-closures.html&quot;&gt;Continue reading on &lt;strong&gt;f2r.github.io&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/php&quot;&gt;#php&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-02T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/why-use-static-closures</id>
    <title>🔗 Why use static closures?</title>
    <updated>2026-04-02T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/type-systems-are-leaky-abstractions-the-case-of-map-take-2"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Adding a type system to Elixir showcases the limits of expressive programming languages. This article examines the proposal of &lt;code&gt;Map.take!/2&lt;/code&gt; and its potential to enhance type safety by raising errors for missing keys in maps.&lt;/p&gt;
&lt;p&gt;The function aims to align with Elixir&apos;s dynamic nature while addressing type correctness. This post highlights challenges in achieving precise type signatures and suggests that utilizing macros may provide a viable solution to ensure static key validation without exposing the type system to unnecessary complexity.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://dashbit.co/blog/type-systems-are-leaky-abstractions-map-take&quot;&gt;Continue reading on &lt;strong&gt;dashbit.co&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-02T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/type-systems-are-leaky-abstractions-the-case-of-map-take-2</id>
    <title>🔗 Type systems are leaky abstractions: the case of Map.take!/2</title>
    <updated>2026-04-02T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/how-do-you-know-if-youre-a-good-leader"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;In this article, self-reflection is highlighted as a crucial aspect of effective leadership. Drawing inspiration from Abraham Lincoln&apos;s introspective writings, it emphasizes that good leaders embrace self-doubt, manage uncertainty, and seek honest feedback from multiple sources.&lt;/p&gt;
&lt;p&gt;Effective leadership isn’t about perfection but recognizing and addressing flaws. Leaders should engage in honest self-evaluation, understanding that their effectiveness is perceived through the experiences of others, not solely their self-assessment.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://mikefisher.substack.com/p/how-do-you-know-if-youre-a-good-leader&quot;&gt;Continue reading on &lt;strong&gt;mikefisher.substack.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-01T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/how-do-you-know-if-youre-a-good-leader</id>
    <title>🔗 How do you know if you’re a good leader?</title>
    <updated>2026-04-01T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/good-software-knows-when-to-stop"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Good software excels by understanding its core purpose and knowing when to stop evolving. This article highlights that effective tools avoid unnecessary complexity and focus on what truly serves users&apos; needs.&lt;/p&gt;
&lt;p&gt;It emphasizes lessons from notable figures in software development, advocating for constraints as advantages. By prioritizing essential features and understanding underlying problems, developers can create better solutions and ensure their software remains impactful without overstretching.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://ogirardot.writizzy.com/p/good-software-knows-when-to-stop&quot;&gt;Continue reading on &lt;strong&gt;ogirardot.writizzy.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-01T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/good-software-knows-when-to-stop</id>
    <title>🔗 Good software knows when to stop</title>
    <updated>2026-04-01T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/using-systemd-units-for-laravel-cronjobs-and-background-processes"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Most Laravel applications require background processes on Linux servers. This article explains how to use &lt;code&gt;systemd&lt;/code&gt; for tasks like cronjobs or queue workers, instead of relying on Supervisor.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;systemd&lt;/code&gt; offers centralized logging, dependency management, and resource limitation benefits. The article details creating service and timer units for running Laravel processes with corresponding examples and commands for verification.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://command-g.nl/en/articles/using-systemd-units-for-cronjobs-and-background-processes&quot;&gt;Continue reading on &lt;strong&gt;command-g.nl&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-04-01T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/using-systemd-units-for-laravel-cronjobs-and-background-processes</id>
    <title>🔗 Using systemd units for Laravel cronjobs and background processes</title>
    <updated>2026-04-01T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/taming-scrollbars-in-a-phoenix-app"/>
    <content type="html">&lt;p&gt;One of those tiny UI details that quietly annoys users more than you&apos;d expect: scrollbars that flash in and out of existence, causing the page layout to jump around. This week I finally cleaned it up in my Phoenix app and the fix was surprisingly elegant.&lt;/p&gt;
&lt;h1&gt;The problem&lt;/h1&gt;
&lt;p&gt;The original &lt;code&gt;root.html.heex&lt;/code&gt; had this on the &lt;code&gt;&lt;html&gt;&lt;/code&gt; element:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;attribute&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;[scrollbar-gutter:stable]&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;scrollbar-gutter: stable&lt;/code&gt; is a CSS property that reserves space for the scrollbar even when it isn&apos;t needed — the idea being to prevent layout shifts when content height changes. It&apos;s a reasonable approach, but it has a side effect: on macOS with &quot;Show scroll bars: Always&quot;, you end up with a permanent empty gutter on pages that don&apos;t scroll. On Windows, where scrollbars are visible by default, the gutter is always there. Depending on your layout, that reserved space can look odd.&lt;/p&gt;
&lt;h1&gt;The solution&lt;/h1&gt;
&lt;p&gt;I removed the Tailwind utility class from the &lt;code&gt;&lt;html&gt;&lt;/code&gt; tag and instead reached for a small CSS snippet:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-css&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;comment&quot;&gt;/* Only show scrollbars when content actually overflows */&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;property&quot;&gt;scrollbar-width&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; thin&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;property&quot;&gt;scrollbar-color&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; transparent transparent&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;hover&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;property&quot;&gt;scrollbar-color&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number-float&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; transparent&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scrollbar-width: thin&lt;/code&gt;&lt;/strong&gt; — uses the browser&apos;s thin scrollbar variant, which is less visually heavy than the default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scrollbar-color: transparent transparent&lt;/code&gt;&lt;/strong&gt; — hides the scrollbar thumb and track by making them fully transparent. The scrollbar doesn&apos;t disappear from the DOM; it just becomes invisible when you&apos;re not interacting with the element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;*:hover &amp;lbrace; scrollbar-color: rgba(0,0,0,0.2) transparent &amp;rbrace;&lt;/code&gt;&lt;/strong&gt; — fades the scrollbar thumb in (as a subtle translucent grey) only when the user hovers over the element. This gives a clean overlay-style scrollbar feel, similar to what macOS does natively.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Why this feels better&lt;/h1&gt;
&lt;p&gt;The end result is a UI that looks clean and uncluttered at rest, but still gives users a clear scrollbar affordance the moment they move their cursor over a scrollable area. No layout jump, no reserved gutter space, and no permanently visible chrome competing for attention.&lt;/p&gt;
&lt;p&gt;It&apos;s a two-file change — one CSS block and the removal of a single Tailwind class — but the visual impact is noticeable, especially on pages with sidebars or nested scrollable containers.&lt;/p&gt;
&lt;h1&gt;Browser support&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;scrollbar-width&lt;/code&gt; and &lt;code&gt;scrollbar-color&lt;/code&gt; are part of the &lt;a href=&quot;https://www.w3.org/TR/css-scrollbars-1/&quot;&gt;CSS Scrollbars Specification&lt;/a&gt; and have solid support in Firefox and Chromium-based browsers. Safari added support in version 18.2. For older Safari versions the scrollbar will just render with its default appearance — a perfectly acceptable fallback.&lt;/p&gt;
&lt;p&gt;Small change, cleaner app. Sometimes that&apos;s all it takes.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/css&quot;&gt;#css&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/html&quot;&gt;#html&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-31T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/taming-scrollbars-in-a-phoenix-app</id>
    <title>🐥 Taming scrollbars in a Phoenix app</title>
    <updated>2026-03-31T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/weaving-stories-with-cascading-workflows"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The newly announced Pro v1.6 RC introduces innovative workflow features, revolutionizing how tasks are coordinated. With its user-friendly design, building workflows requires no specialized knowledge, fostering creativity and spontaneity.&lt;/p&gt;
&lt;p&gt;The article showcases the creation of the &quot;Fire Saga&quot; workflow, which generates children&apos;s stories through generative AI. By employing Oban Workflows, it seamlessly manages task dependencies, making the storytelling process automated and efficient while producing delightful results.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://oban.pro/articles/weaving-stories-with-cascading-workflows&quot;&gt;Continue reading on &lt;strong&gt;oban.pro&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/announcement&quot;&gt;#announcement&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-31T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/weaving-stories-with-cascading-workflows</id>
    <title>🔗 Weaving stories with cascading workflows</title>
    <updated>2026-03-31T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/things-i-miss-about-spring-boot-after-switching-to-go"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;This article compares the advantages of Spring Boot with the simplicity of Go after transitioning from one to the other. It highlights Spring Boot’s extensive features like dependency injection, built-in validations, and a mature ecosystem which greatly aid backend development, whereas Go’s minimalism promotes explicit code and lower resource usage, making deployment easier and faster.&lt;/p&gt;
&lt;p&gt;The discussion emphasizes that Spring Boot offers solid foundational tools for complex applications, such as Spring Security and Spring Data, which facilitate quick implementation of essential backend features. Conversely, Go&apos;s concurrency model and quick startup times are beneficial for lightweight services and applications, stressing that each technology has its strengths based on the system needs.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://sushantdhiman.dev/things-i-miss-about-spring-boot-after-switching-to-go/&quot;&gt;Continue reading on &lt;strong&gt;sushantdhiman.dev&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/golang&quot;&gt;#golang&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/java&quot;&gt;#java&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-31T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/things-i-miss-about-spring-boot-after-switching-to-go</id>
    <title>🔗 Things I miss about Spring Boot after switching to Go</title>
    <updated>2026-03-31T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/a-better-way-using-mkcert-for-https-in-phoenix-on-macos"/>
    <content type="html">&lt;p&gt;In a previous post, I showed how to use &lt;code&gt;mix phx.gen.cert&lt;/code&gt; to set up HTTPS in Phoenix development. While that approach works in theory, in practice it&apos;s a minefield: OpenSSL 3.x generates PKCS12 bundles that macOS&apos;s &lt;code&gt;security&lt;/code&gt; command rejects, browsers send cryptic &lt;code&gt;Decode Error&lt;/code&gt; alerts, and manually trusting certificates in Keychain Access often has no effect at all.&lt;/p&gt;
&lt;p&gt;There&apos;s a much better tool for this: &lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;What makes mkcert different?&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;mkcert&lt;/code&gt; creates a local Certificate Authority (CA) on your machine and registers it with macOS&apos;s system trust store, Firefox, and Chrome in one command. Any certificate you generate from it is automatically trusted — no manual Keychain fiddling required.&lt;/p&gt;
&lt;h1&gt;Step 1: Install mkcert and register the local CA&lt;/h1&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;mkcert&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;mkcert&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-install&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-install&lt;/code&gt; step is what makes everything work. It adds mkcert&apos;s root CA to your system keychain so all browsers trust it going forward.&lt;/p&gt;
&lt;p&gt;Verify it landed:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;security&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;find-certificate&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;mkcert&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Step 2: Generate a certificate for localhost&lt;/h1&gt;
&lt;p&gt;From your Phoenix project root:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;mkcert&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-cert-file&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;priv/cert/selfsigned.pem&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;       &lt;/span&gt;&lt;span class=&quot;variable-parameter&quot;&gt;-key-file&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;priv/cert/selfsigned_key.pem&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;       &lt;/span&gt;&lt;span class=&quot;variable-parameter&quot;&gt;localhost&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;127.0.0.1&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;::1&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a certificate valid for &lt;code&gt;localhost&lt;/code&gt;, &lt;code&gt;127.0.0.1&lt;/code&gt;, and &lt;code&gt;::1&lt;/code&gt;, signed by your local CA.&lt;/p&gt;
&lt;h1&gt;Step 3: Configure Phoenix for HTTPS&lt;/h1&gt;
&lt;p&gt;Update &lt;code&gt;config/dev.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:your_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;YourAppWeb.Endpoint&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;string-special-symbol&quot;&gt;https: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span class=&quot;string-special-symbol&quot;&gt;port: &lt;/span&gt;&lt;span class=&quot;number&quot;&gt;4001&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span class=&quot;string-special-symbol&quot;&gt;cipher_suite: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:strong&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;string-special-symbol&quot;&gt;certfile: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;priv/cert/selfsigned.pem&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;string-special-symbol&quot;&gt;keyfile: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;priv/cert/selfsigned_key.pem&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;string-special-symbol&quot;&gt;check_origin: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span class=&quot;string-special-symbol&quot;&gt;code_reloader: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span class=&quot;string-special-symbol&quot;&gt;debug_errors: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start your server:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;mix&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;phx.server&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit &lt;code&gt;https://localhost:4001&lt;/code&gt; — no browser warnings, no certificate errors, no Keychain gymnastics.&lt;/p&gt;
&lt;h1&gt;What about the cert files in version control?&lt;/h1&gt;
&lt;p&gt;The generated &lt;code&gt;priv/cert/&lt;/code&gt; files are already in &lt;code&gt;.gitignore&lt;/code&gt; when using &lt;code&gt;mix phx.gen.cert&lt;/code&gt;, and should stay there with mkcert too. Each developer on your team runs &lt;code&gt;mkcert -install&lt;/code&gt; and generates their own certificate locally.&lt;/p&gt;
&lt;h1&gt;Upgrading from the old approach&lt;/h1&gt;
&lt;p&gt;If you followed the previous post, you can replace the existing cert files in place — the Phoenix config stays the same since you&apos;re still pointing at &lt;code&gt;priv/cert/selfsigned.pem&lt;/code&gt; and &lt;code&gt;priv/cert/selfsigned_key.pem&lt;/code&gt;. Just regenerate them with mkcert and restart your server.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-30T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/a-better-way-using-mkcert-for-https-in-phoenix-on-macos</id>
    <title>🐥 A better way: Using mkcert for HTTPS in Phoenix on macOS</title>
    <updated>2026-03-30T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/zen"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;When migrating API services from TypeScript to Go, the need for a lightweight HTTP framework that integrates with OpenAPI and offers precise middleware control becomes crucial. Zen emerges as a solution, focusing on simplicity and efficiency by utilizing Go&apos;s standard library without the bloat of complex dependencies.&lt;/p&gt;
&lt;p&gt;The structure of Zen allows for custom middleware execution, ensuring cleaner handlers that are focused solely on business logic. By adopting a schema-first approach for OpenAPI integration, Zen guarantees a rigorous API specification that enhances developer experience while maintaining control over the API contract.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.unkey.com/blog/zen&quot;&gt;Continue reading on &lt;strong&gt;www.unkey.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/golang&quot;&gt;#golang&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/http&quot;&gt;#http&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-30T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/zen</id>
    <title>🔗 A minimalist HTTP library for Go</title>
    <updated>2026-03-30T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/batch-inserts-in-sql-for-large-data-loads"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Large data loads in SQL require efficient insert strategies to enhance performance. Using single row inserts can significantly slow down operations due to overhead from network latency, statement parsing, and transaction commit cycles.&lt;/p&gt;
&lt;p&gt;Batching inserts minimizes these issues by reducing the number of statements sent to the database, leading to higher throughput. This article highlights the importance of transaction management, indexing, and how batch processing can optimize data loading strategies.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://freedium-mirror.cfd/https://medium.com/@AlexanderObregon/batch-inserts-in-sql-for-large-data-loads-b2aef69b0d95&quot;&gt;Continue reading on &lt;strong&gt;freedium-mirror.cfd&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sql&quot;&gt;#sql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-30T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/batch-inserts-in-sql-for-large-data-loads</id>
    <title>🔗 Batch inserts in SQL for large data loads</title>
    <updated>2026-03-30T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/fixing-a-race-condition-in-oban-job-counting-with-telemetry"/>
    <content type="html">&lt;p&gt;When building a LiveView dashboard that shows how many background jobs are still processing, a subtle race condition can make the count permanently off by one. Here&apos;s how I ran into it and how Oban&apos;s telemetry system solved it cleanly.&lt;/p&gt;
&lt;h1&gt;The Setup&lt;/h1&gt;
&lt;p&gt;The app has an Oban worker — &lt;code&gt;ProcessExternalLinkWorker&lt;/code&gt; — that fetches a URL, extracts content, and creates a post. The LiveView index page shows a &quot;X post(s) currently being processed&quot; banner while jobs are in flight.&lt;/p&gt;
&lt;p&gt;The job count is a straightforward Oban query:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;string-special-symbol&quot;&gt;where: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;not in&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;discarded&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;worker&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;worker&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;aggregate&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The LiveView subscribes to a &lt;code&gt;&quot;posts&quot;&lt;/code&gt; PubSub topic and refreshes this count whenever a &lt;code&gt;&quot;post_updated&quot;&lt;/code&gt; message arrives. That message is broadcast from inside &lt;code&gt;Posts.create_post/1&lt;/code&gt;, which is called from within the worker.&lt;/p&gt;
&lt;h1&gt;The Bug&lt;/h1&gt;
&lt;p&gt;Here&apos;s the execution sequence that causes the problem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Oban picks up a job — state transitions to &lt;code&gt;executing&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The worker calls &lt;code&gt;ProcessExternalLink.process_url/1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;That calls &lt;code&gt;Posts.create_post/1&lt;/code&gt;, which broadcasts &lt;code&gt;&quot;post_updated&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The LiveView receives the broadcast and re-queries the job count&lt;/li&gt;
&lt;li&gt;The job is &lt;strong&gt;still &lt;code&gt;executing&lt;/code&gt;&lt;/strong&gt; — it hasn&apos;t returned &lt;code&gt;:ok&lt;/code&gt; yet&lt;/li&gt;
&lt;li&gt;The count includes this job, showing 1 item &quot;still processing&quot; even though the work is done&lt;/li&gt;
&lt;li&gt;The worker returns &lt;code&gt;:ok&lt;/code&gt;, Oban marks the job &lt;code&gt;completed&lt;/code&gt; — but no one tells the LiveView&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The post list refreshes correctly, but the processing counter stays at 1 until the next page load.&lt;/p&gt;
&lt;p&gt;Subtracting 1 from the count isn&apos;t a fix — with multiple concurrent jobs, you&apos;d need to know exactly how many are in this &quot;just finished broadcasting but not yet completed&quot; state, which is unknowable from the outside.&lt;/p&gt;
&lt;h1&gt;The Fix: Oban Telemetry&lt;/h1&gt;
&lt;p&gt;Oban emits telemetry events throughout the job lifecycle. The key one here is &lt;code&gt;[:oban, :job, :stop]&lt;/code&gt;, which fires &lt;strong&gt;after&lt;/strong&gt; the job state has been updated to &lt;code&gt;completed&lt;/code&gt; in the database. There&apos;s also &lt;code&gt;[:oban, :job, :exception]&lt;/code&gt; for failed jobs.&lt;/p&gt;
&lt;p&gt;The fix is to decouple the job count refresh from the &lt;code&gt;&quot;post_updated&quot;&lt;/code&gt; broadcast. Instead, attach a telemetry handler that broadcasts a separate &lt;code&gt;&quot;jobs_updated&quot;&lt;/code&gt; message when a job finishes:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;MyWebApp.ObanTelemetryHandler&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;attach&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;type&quot;&gt;:telemetry&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;oban-job-lifecycle&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;type&quot;&gt;:telemetry&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;attach_many&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span class=&quot;string&quot;&gt;&quot;oban-job-lifecycle&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:oban&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:job&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:stop&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:oban&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:job&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:exception&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span class=&quot;operator&quot;&gt;&amp;&lt;/span&gt;&lt;span class=&quot;constant-builtin&quot;&gt;__MODULE__&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span class=&quot;constant-builtin&quot;&gt;nil&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;handle_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:oban&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:job&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;_measurements&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;_config&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span class=&quot;module&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;Oban job &lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;: worker=&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:worker&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span class=&quot;module&quot;&gt;Phoenix.PubSub&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;broadcast&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;MyWebApp.PubSub&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;posts&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;event: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;jobs_updated&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two design decisions worth noting:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;detach&lt;/code&gt; before &lt;code&gt;attach&lt;/code&gt;&lt;/strong&gt;: Calling &lt;code&gt;attach_many&lt;/code&gt; with a handler ID that&apos;s already registered raises an &lt;code&gt;ArgumentError&lt;/code&gt;. In development, a full server restart re-runs &lt;code&gt;application.ex&lt;/code&gt; and would hit this error on the second start. Calling &lt;code&gt;detach&lt;/code&gt; first makes &lt;code&gt;attach/0&lt;/code&gt; idempotent at the cost of one no-op call.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No worker filter&lt;/strong&gt;: An earlier version filtered on the worker name in the handler&apos;s pattern match. That&apos;s redundant — the Ecto query in the LiveView already scopes the count to the specific worker. Removing the filter keeps the handler simpler and avoids fragility around how Oban formats the worker name in telemetry metadata.&lt;/p&gt;
&lt;p&gt;Call &lt;code&gt;attach/0&lt;/code&gt; in &lt;code&gt;application.ex&lt;/code&gt; after the supervisor starts:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Supervisor&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;start_link&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;module&quot;&gt;MyWebApp.ObanTelemetryHandler&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then handle the new event in the LiveView, separate from &lt;code&gt;&quot;post_updated&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;handle_info&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;event: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;jobs_updated&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;variable&quot;&gt;worker&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;MyWebApp.Workers.ProcessExternalLinkWorker&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;variable&quot;&gt;num_processing&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;function-call&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;where: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;not in&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;discarded&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;keyword-operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;worker&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;worker&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Repo&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;aggregate&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:noreply&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:num_processing&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;num_processing&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Why This Works&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;&quot;post_updated&quot;&lt;/code&gt; broadcast still fires mid-job and the post list still refreshes correctly — that part was never broken. But the job count is now only refreshed in response to the telemetry event, which is guaranteed to fire after the state change has been committed. The LiveView queries at the right moment.&lt;/p&gt;
&lt;p&gt;It also handles failure correctly. If the worker raises an exception, &lt;code&gt;[:oban, :job, :exception]&lt;/code&gt; fires, the LiveView refreshes the count, and any retryable or discarded jobs show up accurately.&lt;/p&gt;
&lt;h1&gt;Takeaway&lt;/h1&gt;
&lt;p&gt;When displaying counts or status derived from job state, don&apos;t trigger the refresh from within the job itself. The job is still running at that point. Instead, hook into Oban&apos;s telemetry events, which fire at well-defined points in the lifecycle after state transitions have been committed.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-29T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/fixing-a-race-condition-in-oban-job-counting-with-telemetry</id>
    <title>🐥 Fixing a race condition in Oban job counting with telemetry</title>
    <updated>2026-03-29T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/why-i-stopped-using-ai-code-editors"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;TL;DR: I chose to make using AI a manual action, because I felt the slow loss of competence over time when I relied on it, and I recommend everyone to be cautious with making AI a key part of their workflow.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In late 2022, I used AI tools for the first time, even before the first version of ChatGPT. In 2023, I started using AI-based tools in my development workflow. Initially, I was super impressed with the capabilities of these LLMs. The fact that I could just copy and paste obscure compiler errors along with the C++ source code, and be told where the error is caused felt like magic.&lt;/p&gt;
&lt;p&gt;Once GitHub Copilot started becoming more and more powerful, I started using it more and more. I used various other LLM integrations right in my editor. Using AI was part of my workflow.&lt;/p&gt;
&lt;p&gt;In late 2024 I removed all LLM integrations from my code editors. I still use LLMs occasionally and I do think AI can be used in a way that is very beneficial for many programmers. So then why don’t I use AI-powered code editing tools?&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://lucianonooijen.com/blog/why-i-stopped-using-ai-code-editors/&quot;&gt;Continue reading on &lt;strong&gt;lucianonooijen.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/github&quot;&gt;#github&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-29T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/why-i-stopped-using-ai-code-editors</id>
    <title>🔗 Why I stopped using AI code editors</title>
    <updated>2026-03-29T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/your-agent-framework-is-just-a-bad-clone-of-elixir-concurrency-lessons-from-telecom-to-ai"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Recently, José Valim published &lt;a href=&quot;https://dashbit.co/blog/why-elixir-best-language-for-ai?ref=georgeguimaraes.com&quot;&gt;&quot;Why Elixir is the Best Language for AI&quot;&lt;/a&gt;, citing a Tencent study showing Elixir achieved the highest LLM code completion rate across 20 languages. Claude Opus 4 scored 80.3% on Elixir problems versus 74.9% for C#, the next-best performer.&lt;/p&gt;
&lt;p&gt;But there&apos;s a deeper argument than &quot;LLMs write good Elixir.&quot; It&apos;s this: &lt;strong&gt;the actor model that Erlang introduced in 1986 is the agent model that AI is rediscovering in 2026&lt;/strong&gt;. Every pattern the Python AI ecosystem is building (isolated state, message passing, supervision hierarchies, fault recovery) already exists in the BEAM virtual machine. And it&apos;s been running telecom switches, WhatsApp, and Discord at scale for decades.&lt;/p&gt;
&lt;p&gt;I&apos;ve been building agentic commerce infrastructure at New Generation. Before that, I shipped a full AI stack serving more than 3 million merchants at a unicorn Brazilian fintech. Both systems run on Elixir. Here&apos;s why that&apos;s not a hipster language choice. It&apos;s an architectural inevitability.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on terminology:&lt;/strong&gt; Throughout this post I refer to &quot;the BEAM.&quot; BEAM is the virtual machine that runs both Erlang and Elixir code, similar to how the JVM runs both Java and Kotlin. Erlang (1986) created the VM and the concurrency model. Elixir (2012) is a modern language built on top of it with better ergonomics. When I say &quot;BEAM,&quot; I mean the runtime and its properties. When I say &quot;Elixir,&quot; I mean the language we write.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://georgeguimaraes.com/your-agent-orchestrator-is-just-a-bad-clone-of-elixir/&quot;&gt;Continue reading on &lt;strong&gt;georgeguimaraes.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/javascript&quot;&gt;#javascript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/typescript&quot;&gt;#typescript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/python&quot;&gt;#python&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-29T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/your-agent-framework-is-just-a-bad-clone-of-elixir-concurrency-lessons-from-telecom-to-ai</id>
    <title>🔗 Your agent framework is just a bad clone of Elixir: concurrency lessons from telecom to AI</title>
    <updated>2026-03-29T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/til-filtering-github-prs-that-are-ready-for-review-and-not-yours"/>
    <content type="html">&lt;p&gt;When reviewing pull requests in GitHub, I often want a clean list of PRs that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;are open&lt;/li&gt;
&lt;li&gt;are not drafts&lt;/li&gt;
&lt;li&gt;are not created by me&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s the filter I use:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;sort:updated-desc is:pr is:open -author:username draft:false
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;What this does&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sort:updated-desc&lt;/code&gt; → most recently updated PRs first&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is:pr&lt;/code&gt; → only pull requests&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is:open&lt;/code&gt; → only open PRs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-author:username&lt;/code&gt; → exclude your own PRs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;draft:false&lt;/code&gt; → exclude draft PRs (only show ready-for-review)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives a focused, high-signal list of PRs that are actually actionable.&lt;/p&gt;
&lt;h1&gt;Bonus: explore more filters&lt;/h1&gt;
&lt;p&gt;GitHub’s search syntax is surprisingly powerful. You can filter by labels, review status, checks, branches, and more.&lt;/p&gt;
&lt;p&gt;Check out the full reference here:
&lt;a href=&quot;https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests&quot;&gt;https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A few useful additions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;review-requested:@me&lt;/code&gt; → PRs requesting your review&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status:success&lt;/code&gt; → only PRs with passing checks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;label:bug&lt;/code&gt; → filter by label&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-is:merged&lt;/code&gt; → exclude merged PRs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you start combining these, you can build very tailored review dashboards directly in GitHub.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/github&quot;&gt;#github&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-28T18:00:00Z</published>
    <id>https://www.yellowduck.be/posts/til-filtering-github-prs-that-are-ready-for-review-and-not-yours</id>
    <title>🐥 TIL: filtering GitHub PRs that are ready for review and not yours</title>
    <updated>2026-03-28T18:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/easily-connecting-to-beam-nodes-in-kubernetes-with-kubectl-beam"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;A kubectl plugin that makes connecting to Erlang and Elixir nodes running in Kubernetes pods as easy as running a single command---with full TTY support and Observer GUI.&lt;/p&gt;
&lt;p&gt;Debugging BEAM applications in Kubernetes has always been more painful than it should be. You know the drill: your Phoenix app is misbehaving in production, you need to inspect some process state, maybe run a few commands in an IEx shell, or fire up Observer to see what’s going on. Simple enough on your local machine, right? But in Kubernetes, you end up manually port-forwarding EPMD, figuring out the dynamic distribution port, wrestling with DNS resolution, and praying your terminal doesn’t get mangled in the process.&lt;/p&gt;
&lt;p&gt;I’ve done this dance too many times—enough to finally build something that handles all the tedious bits automatically. That’s why I created &lt;a href=&quot;https://github.com/codeadict/kubectl-beam&quot;&gt;kubectl-beam&lt;/a&gt;, a kubectl plugin that connects you to BEAM nodes running in Kubernetes pods with a single command.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://dairon.org/connecting-to-beam-nodes-in-kubernetes/&quot;&gt;Continue reading on &lt;strong&gt;dairon.org&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/kubernetes&quot;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-28T14:00:00Z</published>
    <id>https://www.yellowduck.be/posts/easily-connecting-to-beam-nodes-in-kubernetes-with-kubectl-beam</id>
    <title>🔗 Easily connecting to BEAM nodes in Kubernetes with kubectl-beam</title>
    <updated>2026-03-28T14:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/nobody-gets-promoted-for-simplicity"/>
    <content type="html">&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;“Simplicity is a great virtue, but it requires hard work to achieve and education to appreciate. And to make matters worse, complexity sells better.”&lt;br /&gt;
— Edsger Dijkstra&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think there’s something quietly screwing up a lot of engineering teams. In interviews, in promotion packets, in design reviews: the engineer who overbuilds gets a compelling narrative, but the one who ships the simplest thing that works gets… nothing.&lt;/p&gt;
&lt;p&gt;This isn’t intentional, of course. Nobody sits down and says, “let’s make sure the people who over-engineer things get promoted!” But that’s what can happen (and it has been, over and over again) when companies evaluate work incorrectly.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://terriblesoftware.org/2026/03/03/nobody-gets-promoted-for-simplicity/&quot;&gt;Continue reading on &lt;strong&gt;terriblesoftware.org&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-28T09:00:00Z</published>
    <id>https://www.yellowduck.be/posts/nobody-gets-promoted-for-simplicity</id>
    <title>🔗 Nobody gets promoted for simplicity</title>
    <updated>2026-03-28T09:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/enforcing-polymorphic-integrity-in-postgresql-with-num-nonnulls"/>
    <content type="html">&lt;p&gt;Polymorphic associations are common when a single table can reference multiple other tables. A typical implementation is one table with multiple nullable foreign keys:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;my_poly_assocs&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;variable-member&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;bigserial&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;variable-member&quot;&gt;assoc_a_id&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;assoc_a&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;variable-member&quot;&gt;assoc_b_id&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;assoc_b&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;variable-member&quot;&gt;assoc_c_id&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;REFERENCES&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;assoc_c&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intent is simple: each row should reference &lt;strong&gt;exactly one&lt;/strong&gt; of these associations. But the database won’t enforce that automatically. Without extra constraints, you can end up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No association set (all NULL)&lt;/li&gt;
&lt;li&gt;Multiple associations set (invalid state)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostgreSQL has a clean solution for this.&lt;/p&gt;
&lt;h1&gt;The &lt;code&gt;num_nonnulls&lt;/code&gt; function&lt;/h1&gt;
&lt;p&gt;PostgreSQL provides a built-in function called &lt;code&gt;num_nonnulls&lt;/code&gt;. It returns the number of arguments that are not NULL.&lt;/p&gt;
&lt;p&gt;That makes it perfect for enforcing “exactly one” semantics:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;my_poly_assocs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;CONSTRAINT&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; exactly_one_assoc_referenced
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;/span&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;num_nonnulls&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable-member&quot;&gt;assoc_a_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable-member&quot;&gt;assoc_b_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable-member&quot;&gt;assoc_c_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;number-float&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This constraint guarantees:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At least one foreign key is set&lt;/li&gt;
&lt;li&gt;No more than one foreign key is set&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a row violates the rule, the insert or update fails immediately.&lt;/p&gt;
&lt;h1&gt;Why this is better than application-level checks&lt;/h1&gt;
&lt;p&gt;You could enforce this rule in your application layer, but that leaves room for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Race conditions&lt;/li&gt;
&lt;li&gt;Multiple services writing to the same database&lt;/li&gt;
&lt;li&gt;Future code paths forgetting the rule&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;code&gt;CHECK&lt;/code&gt; constraint keeps the invariant inside the database, where it belongs.&lt;/p&gt;
&lt;h1&gt;Variations&lt;/h1&gt;
&lt;p&gt;If your requirement is “at most one” instead of “exactly one”, you can adjust the constraint:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;text&quot;&gt;CHECK &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;num_nonnulls&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;assoc_a_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; assoc_b_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; assoc_c_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;&lt;=&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you later add a new polymorphic target, you must update the constraint to include the new column.&lt;/p&gt;
&lt;h1&gt;When to use this pattern&lt;/h1&gt;
&lt;p&gt;This approach works well when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need strict relational integrity.&lt;/li&gt;
&lt;li&gt;The set of polymorphic targets is finite and known.&lt;/li&gt;
&lt;li&gt;You want predictable query performance without an additional type column.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your targets are dynamic or numerous, a more classic polymorphic design (e.g. &lt;code&gt;target_type&lt;/code&gt; + &lt;code&gt;target_id&lt;/code&gt;) may be more flexible, though it trades off referential integrity.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;num_nonnulls&lt;/code&gt; is a small but powerful feature in PostgreSQL. It allows you to enforce a subtle but important invariant with a single CHECK constraint, keeping your polymorphic associations consistent and safe at the database level.&lt;/p&gt;
&lt;p&gt;It’s one of those features that feels obvious once you know it exists.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sql&quot;&gt;#sql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-27T18:00:00Z</published>
    <id>https://www.yellowduck.be/posts/enforcing-polymorphic-integrity-in-postgresql-with-num-nonnulls</id>
    <title>🐥 Enforcing polymorphic integrity in PostgreSQL with num_nonnulls</title>
    <updated>2026-03-27T18:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link rel="alternate" href="https://www.yellowduck.be/posts/pausing-traffic-and-retrying-in-caddy"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;A pattern I really like for zero-downtime deploys is the ability to &quot;pause&quot; HTTP traffic at the load balancer, such that incoming requests from browsers appear to take a few extra seconds to return, but under the hood they&apos;ve actually been held in a queue while a backend server is swapped out or upgraded in some way.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/simonw/til/blob/main/caddy/pause-retry-traffic.md&quot;&gt;Continue reading on &lt;strong&gt;github.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/http&quot;&gt;#http&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-03-27T14:00:00Z</published>
    <id>https://www.yellowduck.be/posts/pausing-traffic-and-retrying-in-caddy</id>
    <title>🔗 Pausing traffic and retrying in Caddy</title>
    <updated>2026-03-27T14:00:00Z</updated>
  </entry>
</feed>