<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xml:base="https://bjorn.now/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Things I've Learned (TIL) on bjorn.now</title><link>https://bjorn.now/til/</link><description>Recent content in Things I've Learned (TIL) on bjorn.now</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 28 Aug 2026 23:26:33 +0200</lastBuildDate><atom:link href="https://bjorn.now/til/feed.xml" rel="self" type="application/rss+xml"/><item><title>Reset an IKEA Kajplats bulb with 6 power cycles</title><link>https://bjorn.now/til/2026-08-28-reset-an-ikea-kajplats-bulb-with-6-power-cycles/?utm_medium=feed</link><pubDate>Fri, 28 Aug 2026 23:26:33 +0200</pubDate><guid>https://bjorn.now/til/2026-08-28-reset-an-ikea-kajplats-bulb-with-6-power-cycles/</guid><description>Reset an IKEA Kajplats lightbulb by power cycling it 6 times and then leaving it on until it blinks, then it’ll be in pairing mode again. Thanks fishymanbits on Reddit!
Step by step:
Turn off the bulb for 30s Power cycle 6 times; on until it lights up, off immediately, count “one one thousand”, repeat On the sixth on, leave it on and wait When the bulb blinks it’ll be in pairing mode again</description><content:encoded><![CDATA[<p>Reset an IKEA Kajplats lightbulb by power cycling it 6 times and then leaving it on until it blinks, then it&rsquo;ll be in pairing mode again. Thanks <a href="https://www.reddit.com/r/tradfri/comments/1s81s4e/comment/oddqu0g/">fishymanbits on Reddit</a>!</p>
<p>Step by step:</p>
<ol>
<li>Turn off the bulb for 30s</li>
<li>Power cycle 6 times; on until it lights up, off immediately, count &ldquo;one one thousand&rdquo;, repeat</li>
<li>On the sixth on, leave it on and wait</li>
<li>When the bulb blinks it&rsquo;ll be in pairing mode again</li>
</ol>
]]></content:encoded></item><item><title>The IKEA Kajplats bulb wouldn't pair because my network had no IPv6</title><link>https://bjorn.now/til/2026-08-28-the-ikea-kajplats-bulb-wouldnt-pair-because-my-network-had-no-ipv6/?utm_medium=feed</link><pubDate>Fri, 28 Aug 2026 23:13:35 +0200</pubDate><guid>https://bjorn.now/til/2026-08-28-the-ikea-kajplats-bulb-wouldnt-pair-because-my-network-had-no-ipv6/</guid><description>Matter over Thread requires IPv6 on your local network, so if you can’t pair the IKEA Kajplats bulb that might be why.
I didn’t have IPv6 enabled, and my ISP doesn’t assign IPv6, so I used unique-local-ipv6.com to get a unique prefix. Configured it as static in my local network setup, alongside the IPv4 configuration, on my UniFi Dream Machine and then all my bulbs connected.
Now my niece is learning to pronounce English colors as she cycles my apartment through every shade of the rainbow 😁</description><content:encoded><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Matter_(standard)">Matter</a> over <a href="https://en.wikipedia.org/wiki/Thread_(network_protocol)">Thread</a> requires IPv6 on your local network, so if you can&rsquo;t pair the <a href="https://www.ikea.com/nl/en/customer-service/product-support/smart-lighting/smart-lighting-support-pubd8491250/#3eed3150-08de-11f1-90c3-d5e60346fe38">IKEA Kajplats bulb</a> that might be why.</p>
<p>I didn&rsquo;t have IPv6 enabled, and my ISP doesn&rsquo;t assign IPv6, so I used <a href="https://unique-local-ipv6.com/">unique-local-ipv6.com</a> to get a unique prefix. Configured it as <code>static</code> in my local network setup, alongside the IPv4 configuration, on my UniFi Dream Machine and then all my bulbs connected.</p>
<p>Now my niece is learning to pronounce English colors as she cycles my apartment through every shade of the rainbow 😁</p>
]]></content:encoded></item><item><title>beads 1.0.1 silently starts exporting issues.jsonl by default</title><link>https://bjorn.now/til/2026-04-24-beads-1-0-1-flipped-export-auto-default/?utm_medium=feed</link><pubDate>Fri, 24 Apr 2026 19:43:23 +0200</pubDate><guid>https://bjorn.now/til/2026-04-24-beads-1-0-1-flipped-export-auto-default/</guid><description>beads 1.0.1 flipped the export.auto default to on, so old projects silently start exporting issues.jsonl after upgrade. To opt back out, run:
bd config set export.auto false New projects get prompted during init, only existing projects will get this nice surprise.
If you’re using dolt’s git export instead, commit the config change and delete the issues.jsonl file.</description><content:encoded><![CDATA[<p>beads 1.0.1 <a href="https://github.com/gastownhall/beads/commit/64e504d5#diff-8bba7fff92456c4a9bd807232acd9b5eaf6491c2249600c587d4da4b1ba50492R43">flipped the <code>export.auto</code> default to on</a>, so old projects silently start exporting <code>issues.jsonl</code> after upgrade. To opt back out, run:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bd config <span class="nb">set</span> export.auto false</span></span></code></pre></div>
</div>
<p>New projects <a href="https://github.com/gastownhall/beads/commit/64e504d5#diff-7d79935b4facccae363f0f50884254beb035cc02d5289a795d2181d31e4f20efR1638">get prompted during init</a>, only existing projects will get this nice surprise.</p>
<p>If you&rsquo;re using <a href="https://bjorn.now/til/2026-03-20-configure-beads-dolt-to-share-via-git/">dolt&rsquo;s git export</a> instead, commit the config change and delete the <code>issues.jsonl</code> file.</p>
]]></content:encoded></item><item><title>How to recover from a corrupted dolt journal</title><link>https://bjorn.now/til/2026-04-03-how-to-recover-from-a-corrupted-dolt-journal/?utm_medium=feed</link><pubDate>Fri, 03 Apr 2026 13:17:06 +0200</pubDate><guid>https://bjorn.now/til/2026-04-03-how-to-recover-from-a-corrupted-dolt-journal/</guid><description>When dolt’s journal is corrupted, dolt fsck --revive-journal-with-data-loss will truncate it back to the last valid record.
I found this error message when starting dolt up after upgrading the server: invalid journal record at offset 5831169: invalid journal record: CRC checksum does not match.
To recover, go to your dolt DB:
cd ~/.beads/shared-server/dolt/&lt;db&gt; \ &amp;&amp; dolt fsck --revive-journal-with-data-loss It backs up the journal, truncates to the point it failed, and makes it possible to start the server again. In my case the db came back up with data loss. I did a bd dolt pull and it pulled in what I had pushed most recently, so nothing major was lost.</description><content:encoded><![CDATA[<p>When dolt&rsquo;s journal is corrupted, <code>dolt fsck --revive-journal-with-data-loss</code> will truncate it back to the last valid record.</p>
<p>I found this error message when starting dolt up after upgrading the server: <code>invalid journal record at offset 5831169: invalid journal record: CRC checksum does not match</code>.</p>
<p>To recover, go to your dolt DB:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ~/.beads/shared-server/dolt/&lt;db&gt; <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="o">&amp;&amp;</span> dolt fsck --revive-journal-with-data-loss</span></span></code></pre></div>
</div>
<p>It backs up the journal, truncates to the point it failed, and makes it possible to start the server again. In my case the db came back up with data loss. I did a <code>bd dolt pull</code> and it pulled in what I had pushed most recently, so nothing major was lost.</p>
<p>I&rsquo;m guessing this happened because installing beads 1.0 also upgraded dolt. If you&rsquo;re upgrading, it&rsquo;s worth making sure you&rsquo;ve done <code>bd vc commit &amp;&amp; bd dolt push</code> in all repos beforehand. That means <a href="https://bjorn.now/til/2026-03-20-configure-beads-dolt-to-share-via-git/">configuring a git remote</a> first.</p>
]]></content:encoded></item><item><title>How to find which dolt databases need to be committed</title><link>https://bjorn.now/til/2026-03-26-how-to-find-which-dolt-databases-need-to-be-committed/?utm_medium=feed</link><pubDate>Thu, 26 Mar 2026 15:32:42 +0100</pubDate><guid>https://bjorn.now/til/2026-03-26-how-to-find-which-dolt-databases-need-to-be-committed/</guid><description>dolt’s shared server won’t pull until all databases on the server have committed changes, not just the beads project you’re pulling.
You’ll get Error 1105 (HY000): cannot merge with uncommitted changes and it’s confusing when the beads project you’re working in has autocommit on. The uncommitted changes are in a different project that’s also using the shared server.
The fix is to go through your other projects and commit their changes (or enable autocommit by adding dolt.auto-commit: "on" to .beads/config.yml). To find which databases have uncommitted changes:</description><content:encoded><![CDATA[<p>dolt&rsquo;s shared server won&rsquo;t pull until all databases on the server have committed changes, not just the beads project you&rsquo;re pulling.</p>
<p>You&rsquo;ll get <code>Error 1105 (HY000): cannot merge with uncommitted changes</code> and it&rsquo;s confusing when the beads project you&rsquo;re working in has autocommit on. The uncommitted changes are in a different project that&rsquo;s also using the shared server.</p>
<p>The fix is to go through your other projects and commit their changes (or enable autocommit by adding <code>dolt.auto-commit: &quot;on&quot;</code> to <code>.beads/config.yml</code>). To find which databases have uncommitted changes:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">query</span><span class="o">=</span><span class="k">$(</span>bd sql <span class="s2">&#34;SHOW DATABASES&#34;</span> 2&gt;/dev/null <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> grep -v -E <span class="s2">&#34;^(Database|--|\(|dolt|information_schema|mysql)&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> db<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;SELECT &#39;</span><span class="nv">$db</span><span class="s2">&#39; as db, table_name, status FROM \`</span><span class="nv">$db</span><span class="s2">\`.dolt_status WHERE table_name != &#39;config&#39;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">done</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> paste -sd<span class="s1">&#39;|&#39;</span> - <span class="p">|</span> sed <span class="s1">&#39;s/|/ UNION ALL /g&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">bd sql <span class="s2">&#34;</span><span class="nv">$query</span><span class="s2">&#34;</span></span></span></code></pre></div>
</div>
<p>For each project that shows up, go in and run <code>bd vc commit -m 'Snapshot'</code>, and then you can pull again.</p>
<p>We&rsquo;re ignoring changes to the <code>config</code> table because they don&rsquo;t seem to matter for pulling.</p>
]]></content:encoded></item><item><title>How to manually restore a dolt db from its git remote</title><link>https://bjorn.now/til/2026-03-23-how-to-manually-restore-a-dolt-db-from-its-git-remote/?utm_medium=feed</link><pubDate>Mon, 23 Mar 2026 16:02:49 +0100</pubDate><guid>https://bjorn.now/til/2026-03-23-how-to-manually-restore-a-dolt-db-from-its-git-remote/</guid><description>You can manually clone a dolt database from its git remote when bd bootstrap or bd init won’t cooperate.
bd dolt stop: stop the server in the git repo you’re restoring so you can operate on its database cd ~/.beads/shared-server/dolt: go to the shared dolt server (it’s .beads/dolt/beads otherwise, I think) rm -rf &lt;prefix&gt;: remove the DB if it exists already dolt clone git+ssh://git@github.com/user/repo.git &lt;prefix&gt;: clone it to your prefix cd - &amp;&amp; bd ready: see if you can see the listing I don’t know why my local just refused with bd bootstrap but this allowed me to fix it manually.</description><content:encoded><![CDATA[<p>You can manually clone a dolt database from its git remote when <code>bd bootstrap</code> or <code>bd init</code> won&rsquo;t cooperate.</p>
<ol>
<li><code>bd dolt stop</code>: stop the server in the git repo you&rsquo;re restoring so you can operate on its database</li>
<li><code>cd ~/.beads/shared-server/dolt</code>: go to the shared dolt server (it&rsquo;s <code>.beads/dolt/beads</code> otherwise, I think)</li>
<li><code>rm -rf &lt;prefix&gt;</code>: remove the DB if it exists already</li>
<li><code>dolt clone git+ssh://git@github.com/user/repo.git &lt;prefix&gt;</code>: clone it to your prefix</li>
<li><code>cd - &amp;&amp; bd ready</code>: see if you can see the listing</li>
</ol>
<p>I don&rsquo;t know why my local just refused with <code>bd bootstrap</code> but this allowed me to fix it manually.</p>
]]></content:encoded></item><item><title>How to migrate beads from per-project to shared dolt server</title><link>https://bjorn.now/til/2026-03-20-migrate-beads-to-shared-dolt-server/?utm_medium=feed</link><pubDate>Fri, 20 Mar 2026 12:50:24 +0100</pubDate><guid>https://bjorn.now/til/2026-03-20-migrate-beads-to-shared-dolt-server/</guid><description>To switch beads from a per-project dolt server to the shared server, do a backup, configure shared-server, and restore.
I was running in “a server per repo” mode and it’s just unnecessary, they clash in annoying ways when switching repos, and once I understood how beads store data in git I embraced dolt and the shared server. I had hesitated because I had somehow connected it with DoltHub, and that’s optional for using beads.</description><content:encoded><![CDATA[<p>To switch beads from a per-project dolt server to the shared server, do a backup, configure shared-server, and restore.</p>
<p>I was running in &ldquo;a server per repo&rdquo; mode and it&rsquo;s just unnecessary, they clash in annoying ways when switching repos, and once I understood how <a href="/til/2026-03-20-configure-beads-dolt-to-share-via-git/">beads store data in git</a> I embraced dolt and the shared server. I had hesitated because I had somehow connected it with DoltHub, and that&rsquo;s optional for using beads.</p>
<ol>
<li><code>bd backup</code>: make sure you have your local database available for restore</li>
<li><code>bd dolt stop</code>: so we don&rsquo;t conflict on anything</li>
<li><code>bd dolt set shared-server true</code>: to configure this project to use the shared server</li>
<li><code>bd restore</code>: to restore the db</li>
</ol>
<p>For most of my projects this just worked, but in some cases I had to <a href="/til/2026-03-20-how-to-reset-beads-dolt-database/">reset the dolt configuration for the repo</a>.</p>
]]></content:encoded></item><item><title>Configure beads dolt to share via git</title><link>https://bjorn.now/til/2026-03-20-configure-beads-dolt-to-share-via-git/?utm_medium=feed</link><pubDate>Fri, 20 Mar 2026 12:31:05 +0100</pubDate><guid>https://bjorn.now/til/2026-03-20-configure-beads-dolt-to-share-via-git/</guid><description>Beads with dolt store data in git refs, not branches, and won’t share it anywhere until you manually configure a remote.
After bd init, your dolt data only exists locally. You can check with bd dolt remote list which will show “No remotes configured.”</description><content:encoded><![CDATA[<p>Beads with dolt <a href="https://github.com/steveyegge/beads/blob/main/docs/DOLT.md">store data</a> in git refs, not branches, and won&rsquo;t share it anywhere until you manually configure a remote.</p>
<p>After <code>bd init</code>, your dolt data only exists locally. You can check with <code>bd dolt remote list</code> which will show &ldquo;No remotes configured.&rdquo;</p>
<p>Point it at your existing git repo:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bd dolt remote add origin git+ssh://git@github.com/you/repo.git</span></span></code></pre></div>
</div>
<p>Then push and verify:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bd dolt push
</span></span><span class="line"><span class="cl">git ls-remote origin <span class="s1">&#39;refs/dolt/*&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 52689c0ae7...  refs/dolt/data</span></span></span></code></pre></div>
</div>
<p>I&rsquo;ve seen <code>refs/</code> used for GitHub PRs before (<code>refs/pr/&lt;num&gt;/head</code>) but I haven&rsquo;t looked into how any of it actually works. For now, seeing <code>refs/dolt/data</code> show up in <code>git ls-remote</code> is enough to make me comfortable that my data exists somewhere.</p>
<p>To avoid doing this manually on every machine, add the remote to <code>.beads/config.yaml</code>:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">sync</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">git-remote</span><span class="p">:</span><span class="w"> </span><span class="l">git+ssh://git@github.com/you/repo.git</span></span></span></code></pre></div>
</div>
<p>Anyone cloning the repo can then run <code>bd bootstrap</code> and <code>bd dolt push / pull</code> will just work.</p>]]></content:encoded></item><item><title>How I configure beads for new projects</title><link>https://bjorn.now/til/2026-03-20-how-i-configure-beads-for-new-projects/?utm_medium=feed</link><pubDate>Fri, 20 Mar 2026 12:31:05 +0100</pubDate><guid>https://bjorn.now/til/2026-03-20-how-i-configure-beads-for-new-projects/</guid><description>I tweak the default beads setup to work with Claude instead of the generic agent integration.
As of beads 0.62.0, this is what I run:
# configure agents later bd init --shared-server --skip-agents # setup a CLAUDE.md and configure claude hooks to re-prime beads bd setup claude --project bd hooks install # move local settings to project settings mv .claude/settings{.local,}.json # amend init commit so .beads-credential-key never enters git git add .claude/settings.json CLAUDE.md git commit --amend -CHEAD # share beads data alongside your git repo bd dolt remote add origin git+ssh://git@github.com/you/repo.git echo -e 'sync:\n git-remote: git+ssh://git@github.com/you/repo.git' &gt;&gt; .beads/config.yaml The key thing is --skip-agents and then bd setup claude --project instead, so you get Claude-specific hooks rather than the generic agent integration. The dolt remote lines are so your beads data gets stored alongside your git repo.</description><content:encoded><![CDATA[<p>I tweak the default beads setup to work with Claude instead of the generic agent integration.</p>
<p>As of beads 0.62.0, this is what I run:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># configure agents later</span>
</span></span><span class="line"><span class="cl">bd init --shared-server --skip-agents
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># setup a CLAUDE.md and configure claude hooks to re-prime beads</span>
</span></span><span class="line"><span class="cl">bd setup claude --project
</span></span><span class="line"><span class="cl">bd hooks install
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># move local settings to project settings</span>
</span></span><span class="line"><span class="cl">mv .claude/settings<span class="o">{</span>.local,<span class="o">}</span>.json
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># amend init commit so .beads-credential-key never enters git</span>
</span></span><span class="line"><span class="cl">git add .claude/settings.json CLAUDE.md
</span></span><span class="line"><span class="cl">git commit --amend -CHEAD
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># share beads data alongside your git repo</span>
</span></span><span class="line"><span class="cl">bd dolt remote add origin git+ssh://git@github.com/you/repo.git
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s1">&#39;sync:\n  git-remote: git+ssh://git@github.com/you/repo.git&#39;</span> &gt;&gt; .beads/config.yaml</span></span></code></pre></div>
</div>
<p>The key thing is <code>--skip-agents</code> and then <code>bd setup claude --project</code> instead, so you get Claude-specific hooks rather than the generic agent integration. The <a href="/til/2026-03-20-configure-beads-dolt-to-share-via-git/">dolt remote lines</a> are so your beads data gets stored alongside your git repo.</p>
]]></content:encoded></item><item><title>How to reset beads' dolt database</title><link>https://bjorn.now/til/2026-03-20-how-to-reset-beads-dolt-database/?utm_medium=feed</link><pubDate>Fri, 20 Mar 2026 11:29:14 +0100</pubDate><guid>https://bjorn.now/til/2026-03-20-how-to-reset-beads-dolt-database/</guid><description>When beads can’t find or create a dolt database, delete the local dolt state and reinit.
The errors look like this:
Error: failed to open Dolt store: database "bs" not available after CREATE DATABASE: Error 1049 (HY000): database not found: bs or:
Error: failed to open database: database "bs" not found on Dolt server at 127.0.0.1:3308</description><content:encoded><![CDATA[<p>When <a href="https://github.com/steveyegge/beads">beads</a> can&rsquo;t find or create a <a href="https://github.com/dolthub/dolt">dolt</a> database, delete the local dolt state and reinit.</p>
<p>The errors look like this:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Error: failed to open Dolt store: database &#34;bs&#34; not available after CREATE DATABASE: Error 1049 (HY000): database not found: bs</span></span></code></pre></div>
</div>
<p>or:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Error: failed to open database: database &#34;bs&#34; not found on Dolt server at 127.0.0.1:3308</span></span></code></pre></div>
</div>
<p>Fix it by stopping dolt and removing its local state:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bd dolt stop  <span class="c1"># or you won&#39;t be able to delete all the files</span>
</span></span><span class="line"><span class="cl">rm -rf .beads/dolt/
</span></span><span class="line"><span class="cl">rm .beads/dolt-server.*</span></span></code></pre></div>
</div>
<p>Also check <code>.beads/metadata.json</code> for stale server connection config. Mine had extra fields that were messing things up. I cleaned it down to just the essentials:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;database&#34;</span><span class="p">:</span> <span class="s2">&#34;dolt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;backend&#34;</span><span class="p">:</span> <span class="s2">&#34;dolt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dolt_mode&#34;</span><span class="p">:</span> <span class="s2">&#34;server&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dolt_database&#34;</span><span class="p">:</span> <span class="s2">&#34;bs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;project_id&#34;</span><span class="p">:</span> <span class="s2">&#34;deadbeef-9164-426d-8c1c-ff8ae5d35e3e&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>Then reinit: <code>bd init --force --prefix &lt;yourprefix&gt;</code>.</p>
<p><code>bd doctor --fix</code> couldn&rsquo;t sort this out, which is why I ended up doing all this manual cleanup.</p>
<p>I hit this upgrading to beads 0.61.0 when switching to shared-server mode.</p>]]></content:encoded></item><item><title>Smallest test print first</title><link>https://bjorn.now/til/2026-03-19-smallest-test-print-first/?utm_medium=feed</link><pubDate>Thu, 19 Mar 2026 12:55:19 +0100</pubDate><guid>https://bjorn.now/til/2026-03-19-smallest-test-print-first/</guid><description>Start with the smallest test print you can, no, even smaller, when iterating on a 3D model.
Top: the first print, fully believing I had nailed everything on the first try (also some hope, it was printing over night so hey, it might mean I’m done with this first design) Middle: second print, cut smaller but enough to let the device rest. Made me realize I hadn’t taken into account that the device tapers at the top and bottom, so it needs less living space Bottom: latest print, cut to print as small as possible, and maybe too small, but it seems to indicate this is good. Next up, a full print Next time, reverse the order 😅</description><content:encoded><![CDATA[<p>Start with the smallest test print you can, <a href="https://www.geepawhill.org/series/many-more-much-smaller-steps/">no, even smaller</a>, when iterating on a 3D model.</p>
<figure><img src="/img/2026/03-evolution-of-3d-test-prints.jpeg"
    alt="Three 3D printed gridfinity bins in decreasing size, showing the iteration from full print to smallest possible test piece">
</figure>

<ul>
<li><strong>Top:</strong> the first print, fully believing I had nailed everything on the first try (also some hope, it was printing over night so hey, it might mean I&rsquo;m done with this first design)</li>
<li><strong>Middle:</strong> second print, cut smaller but enough to let the device rest. Made me realize I hadn&rsquo;t taken into account that the device tapers at the top and bottom, so it needs less living space</li>
<li><strong>Bottom:</strong> latest print, cut to print as small as possible, and maybe too small, but it seems to indicate this is good. Next up, a full print</li>
</ul>
<p>Next time, reverse the order 😅</p>
]]></content:encoded></item><item><title>git rm --cached &lt;file&gt; will remove from git but not from the filesystem</title><link>https://bjorn.now/til/2026-03-01-git-rm-cached-will-remove-something-from-git-but-not-the-filesystem/?utm_medium=feed</link><pubDate>Sun, 01 Mar 2026 22:06:44 +0100</pubDate><guid>https://bjorn.now/til/2026-03-01-git-rm-cached-will-remove-something-from-git-but-not-the-filesystem/</guid><description>git rm --cached &lt;file&gt; will remove a file from git’s tracking but leave it on the filesystem. Great if you accidentally commit something that should’ve been in .gitignore.</description><content:encoded><![CDATA[<p><code>git rm --cached &lt;file&gt;</code> will remove a file from git&rsquo;s tracking but leave it on the filesystem. Great if you accidentally commit something that should&rsquo;ve been in <code>.gitignore</code>.</p>
]]></content:encoded></item><item><title>You don't have to symlink git hooks, move the hooks folder to your scripts</title><link>https://bjorn.now/til/2026-02-26-you-can-move-gits-hook-folder/?utm_medium=feed</link><pubDate>Thu, 26 Feb 2026 14:35:58 +0100</pubDate><guid>https://bjorn.now/til/2026-02-26-you-can-move-gits-hook-folder/</guid><description>Use git config core.hooksPath script/hooks and it’ll use the scripts in script/hooks as hooks in the git project. Version controlled, and no need to update symlinks as scripts come and go.</description><content:encoded><![CDATA[<p>Use <code>git config core.hooksPath script/hooks</code> and it&rsquo;ll use the scripts in <code>script/hooks</code> as hooks in the git project. Version controlled, and no need to update symlinks as scripts come and go.</p>
<p>The way I have historically managed the hooks is by having a <code>script/bootstrap</code> that symlinks scripts, and then hopefully my team mates run bootstrap when something changes.</p>
<p>So this is a nice way of making that entire problem go away.</p>]]></content:encoded></item><item><title>If n8n shows invalid date error it might be Because of missing timezone config</title><link>https://bjorn.now/til/2026-01-28-if-n8n-has-invalid-date-it-might-be-because-of-missing-timezone/?utm_medium=feed</link><pubDate>Wed, 28 Jan 2026 14:00:20 +0100</pubDate><guid>https://bjorn.now/til/2026-01-28-if-n8n-has-invalid-date-it-might-be-because-of-missing-timezone/</guid><description>Set GENERIC_TIMEZONE for n8n or you’ll get ERROR: You specified an invalid date. when you try to publish a time triggered workflow.
I had done a server migration and completely missed the .env file when copying, and after recreating it all started working.</description><content:encoded><![CDATA[<p>Set <code>GENERIC_TIMEZONE</code> for n8n or you&rsquo;ll get <code>ERROR: You specified an invalid date.</code> when you try to publish a time triggered workflow.</p>
<p>I had done a server migration and completely missed the <code>.env</code> file when copying, and <a href="https://docs.n8n.io/hosting/installation/server-setups/docker-compose/#4-create-an-env-file">after recreating it</a> all started working.</p>
]]></content:encoded></item><item><title>Claude Code sandbox exclusion requires both permissions.allow and sandbox.excludedCommands</title><link>https://bjorn.now/til/2026-01-10-claude-code-sandbox-exclusion-requires-both-permissions-allow-and-sandbox-excludedcommands/?utm_medium=feed</link><pubDate>Sat, 10 Jan 2026 14:30:41 +0100</pubDate><guid>https://bjorn.now/til/2026-01-10-claude-code-sandbox-exclusion-requires-both-permissions-allow-and-sandbox-excludedcommands/</guid><description>Add commands to both permissions.allow and sandbox.excludedCommands to run them outside Claude Code’s sandbox by default, because allow grants permission to run at all and excludedCommands runs them unsandboxed.
{ "permissions": { "allow": [ "Bash(pnpm test:unit:*)", "Bash(pnpm test:e2e:*)" ] }, "sandbox": { "excludedCommands": [ "pnpm test:unit:*", // Same commands, different purpose "pnpm test:e2e:*" ] } } Note: Tested with Claude Code v2.1.3</description><content:encoded><![CDATA[<p>Add commands to both <code>permissions.allow</code> and <code>sandbox.excludedCommands</code> to run them outside <a href="https://code.claude.com/docs/en/settings#sandbox-settings">Claude Code&rsquo;s sandbox</a> by default, because <code>allow</code> grants permission to run at all and <code>excludedCommands</code> runs them unsandboxed.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;permissions&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;allow&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm test:unit:*)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm test:e2e:*)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;sandbox&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;excludedCommands&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;pnpm test:unit:*&#34;</span><span class="p">,</span>    <span class="c1">// Same commands, different purpose
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="s2">&#34;pnpm test:e2e:*&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p><strong>Note:</strong> Tested with Claude Code v2.1.3</p>]]></content:encoded></item><item><title>Write out colon subcommands explicitly because wildcard colons trigger prefix matching</title><link>https://bjorn.now/til/2026-01-10-write-out-colon-subcommands-explicitly-because-wildcard-colons-trigger-prefix-matching/?utm_medium=feed</link><pubDate>Sat, 10 Jan 2026 14:07:39 +0100</pubDate><guid>https://bjorn.now/til/2026-01-10-write-out-colon-subcommands-explicitly-because-wildcard-colons-trigger-prefix-matching/</guid><description>Write out colon subcommands explicitly for Claude Code permissions because test:* doesn’t match test:unit (prefix matching treats them as separate commands), and the fallback test* is too broad (matches testAndDestroyEnvironment).
For example:
{ "permissions": { "allow": [ "Bash(pnpm test:unit:*)", // Allows pnpm test:unit --arg etc. "Bash(pnpm test:e2e:*)", "Bash(pnpm test*)" // ❌ &lt;- will allow testAndDestroyEnvironment ] } } Note: Tested with Claude Code v2.1.3</description><content:encoded><![CDATA[<p>Write out colon subcommands explicitly for <a href="https://code.claude.com/docs/en/iam#tool-specific-permission-rules">Claude Code permissions</a> because <code>test:*</code> doesn&rsquo;t match <code>test:unit</code> (prefix matching treats them as separate commands), and the fallback <code>test*</code> is too broad (matches <code>testAndDestroyEnvironment</code>).</p>
<p>For example:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;permissions&#34;</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;allow&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm test:unit:*)&#34;</span><span class="p">,</span> <span class="c1">// Allows pnpm test:unit --arg etc.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="s2">&#34;Bash(pnpm test:e2e:*)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm test*)&#34;</span>        <span class="c1">// ❌ &lt;- will allow testAndDestroyEnvironment
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p><strong>Note:</strong> Tested with Claude Code v2.1.3</p>]]></content:encoded></item><item><title>Use Jetbrains IntelliJ Source Roots to strip prefixes from copied paths</title><link>https://bjorn.now/til/2025-11-04-use-jetbrains-intellij-source-roots-to-strip-prefixes-from-copied-paths/?utm_medium=feed</link><pubDate>Tue, 04 Nov 2025 12:13:45 +0100</pubDate><guid>https://bjorn.now/til/2025-11-04-use-jetbrains-intellij-source-roots-to-strip-prefixes-from-copied-paths/</guid><description>Marking the content/ folder for your Hugo site as a “Source Root” in IntelliJ lets you use Path from Source Root to get the path to the file without the content/ prefix you don’t need when linking internally.
To make linking internally easier, I created an Alfred app snippet ({{&lt; relref “{clipboard}” &gt;}}), so now I only have to open the other post, double-tap shift, type “source root”, then run the snippet, and all linked up.</description><content:encoded><![CDATA[<p>Marking the <code>content/</code> folder for your Hugo site as a &ldquo;Source Root&rdquo; in IntelliJ lets you use <code>Path from Source Root</code> to get the path to the file without the <code>content/</code> prefix you don&rsquo;t need when linking internally.</p>
<p>To make linking internally easier, I created <a href="/blog/2024/04/15/day-to-day-automation-using-alfred/#text-replacement--snippets">an Alfred app snippet</a> (<code>{{&lt; relref &ldquo;{clipboard}&rdquo; &gt;}}</code>), so now I only have to open the other post, double-tap shift, type &ldquo;source root&rdquo;, then run the snippet, and all linked up.</p>
<figure><img src="/img/2025/11-intellij-copy-path.png"
    alt="IntelliJ context menu showing Copy Path/Reference options with &#39;Path from Source Root&#39; highlighted"><figcaption>
      <p>Right-click folder → Mark Directory as → Sources Root.<br>To copy path: open file and search &lsquo;source root&rsquo;, or right-click → Copy Path/Reference → Path from Source Root</p>
    </figcaption>
</figure>

]]></content:encoded></item><item><title>Use `/@&lt;name&gt;` to access the value of an attribute when you've selected an element with XPath.</title><link>https://bjorn.now/til/2025-11-03-use-forward-slash-at-name-to-access-property-of-an-element-in-xpath.md/?utm_medium=feed</link><pubDate>Mon, 03 Nov 2025 14:59:25 +0100</pubDate><guid>https://bjorn.now/til/2025-11-03-use-forward-slash-at-name-to-access-property-of-an-element-in-xpath.md/</guid><description>Use /@&lt;name&gt; to access the value of an attribute when you’ve selected an element with XPath.
With the expression //*[@class="published-at"] you will get the HTML tag that has class="published-at", and if you then want the datetime property you add /@datetime to get it.</description><content:encoded><![CDATA[<p>Use <code>/@&lt;name&gt;</code> to access the value of an attribute when you&rsquo;ve selected an element with XPath.</p>
<p>With the expression <code>//*[@class=&quot;published-at&quot;]</code> you will get the HTML tag that has <code>class=&quot;published-at&quot;</code>, and if you then want the <code>datetime</code> property you add <code>/@datetime</code> to get it.</p>
]]></content:encoded></item><item><title>Apple Notes can do calculations with variables</title><link>https://bjorn.now/til/2025-10-26-apple-notes-can-do-calculations-with-variables/?utm_medium=feed</link><pubDate>Sun, 26 Oct 2025 12:35:12 +0100</pubDate><guid>https://bjorn.now/til/2025-10-26-apple-notes-can-do-calculations-with-variables/</guid><description>Apple Notes can evaluate calculations with variables that update as you change them. The variable name has to be “one word,” so usual camelCase or PascalCase will likely do you best.
I realized this while trying to calculate what height TV bench I should get:
The center of the TV should be about eye level, so what height bench should I get given myself and the sofa? Seems around 60cm will be good. Update: I realized the TV stand adds to the height of the TV bench, so it shouldn’t be part of the center of TV calculation.</description><content:encoded><![CDATA[<p>Apple Notes can evaluate calculations with variables that update as you change them.
The variable name has to be &ldquo;one word,&rdquo;
so usual <code>camelCase</code> or <code>PascalCase</code> will likely do you best.</p>
<p>I realized this while trying to calculate what height TV bench I should get:</p>
<figure><img src="/img/2025/10-tv-bench-calculations.jpeg"
    alt="Apple Notes calculations determining TV bench height using variables for TV size, sofa height, and eye level"><figcaption>
      <p>The center of the TV should be about eye level, so what height bench should I get given myself and the sofa? Seems around 60cm will be good. <strong>Update:</strong> I realized the TV stand adds to the height of the TV bench, so it shouldn&rsquo;t be part of the center of TV calculation.</p>
    </figcaption>
</figure>

<p>Annoyingly, you can&rsquo;t declare a variable and see its result in one step.
I found two options for this, and you decide which you prefer.</p>
<p>If you&rsquo;re okay with an extra line you can show it by declaring the variable again, just let notes fill in the part after the <code>=</code>:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">MyValue=25*4
</span></span><span class="line"><span class="cl">MyValue=100</span></span></code></pre></div>
</div>
<p>Or, instead, write the calculation first,
and let Notes show the result, then add the variable name.</p>
<p>First, do the calculation:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">25*4=&lt;wait for Notes to autofill&gt;</span></span></code></pre></div>
</div>
<p>Second, name the variable by adding the name and <code>=</code> at the front of the line:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">MyValue=25*4=100</span></span></code></pre></div>
</div>
<p>I naturally ended up doing this second while I was working it through,
since I wanted to see the result first, and then I named the variable.</p>
<p>Notes also handle some <a href="https://support.apple.com/en-sg/guide/iphone/iph69d274dde/26/ios/26">unit conversions</a> for you:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">The average daily temperature in Singapore as Fahrenheit is: 
</span></span><span class="line"><span class="cl">0f + 32c = 89.6f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">How many Swedish Krona do I get from one SGD right now?
</span></span><span class="line"><span class="cl">0sek + 1sgd = 7.22 SEK</span></span></code></pre></div>
</div>
<p>I started on my phone taking Notes,
and was surprised to realize I didn&rsquo;t have to fire up Python on my computer to finish it.
A new useful tool that I always carry with me. 🙂</p>
<hr>
<p>Documentation links to Apple:</p>
<ul>
<li><a href="https://support.apple.com/en-sg/guide/iphone/iphb9c2b948f/ios">for iOS</a></li>
<li><a href="https://support.apple.com/en-sg/guide/notes/apda85974595/mac">for macOS</a></li>
</ul>
]]></content:encoded></item><item><title>Don't quote regex patterns in Bash [[ ]] tests, because they'll match literally</title><link>https://bjorn.now/til/2025-10-09-dont-quote-regex-patterns-in-bash-tests-because-theyll-match-literally/?utm_medium=feed</link><pubDate>Thu, 09 Oct 2025 11:50:33 +0200</pubDate><guid>https://bjorn.now/til/2025-10-09-dont-quote-regex-patterns-in-bash-tests-because-theyll-match-literally/</guid><description>Don’t quote regex patterns in Bash [[ ]] tests, because they’ll match literally.
So the below won’t match because it’s looking for a literal $ in the string, instead of matching at the end of the string:
file="https://example.com/blog-post/index.html" [[ "$file" =~ "/index.html$" ]] &amp;&amp; echo MATCH But this will:
file="https://example.com/blog-post/index.html" [[ "$file" =~ /index\.html$ ]] &amp;&amp; echo MATCH Note: [[ ]] is a Bash extension of the test command that supports regexes (among other things). The [ ] is the POSIX standard test (see man test), so [[ ]] features may not work in other shells or in limited shells like ash or sh.</description><content:encoded><![CDATA[<p>Don&rsquo;t quote regex patterns in Bash <code>[[ ]]</code> <a href="https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html#index-_005b_005b">tests</a>, because they&rsquo;ll match literally.</p>
<p>So the below won&rsquo;t match because it&rsquo;s looking for a literal <code>$</code> in the string,
instead of matching at the end of the string:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">file</span><span class="o">=</span><span class="s2">&#34;https://example.com/blog-post/index.html&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> <span class="o">=</span>~ <span class="s2">&#34;/index.html</span>$<span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> MATCH</span></span></code></pre></div>
</div>
<p>But this will:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">file</span><span class="o">=</span><span class="s2">&#34;https://example.com/blog-post/index.html&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> <span class="o">=</span>~ /index<span class="se">\.</span>html$ <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> MATCH</span></span></code></pre></div>
</div>
<p><strong>Note:</strong> <code>[[ ]]</code> is a Bash extension of the test command that supports regexes (among other things).
The <code>[ ]</code> is the POSIX standard test (see <code>man test</code>),
so <code>[[ ]]</code> features may not work in other shells or in limited shells like ash or sh.</p>
<p>I learned this while removing <code>index.html</code> from file paths to bust Cloudflare&rsquo;s cache for directory listings/pretty URLs.
I then used <a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">parameter expansion</a> to replace the match with an empty string, so directory URLs are also purged:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="si">${</span><span class="nv">file</span><span class="p">/index.html/</span><span class="si">}</span></span></span></code></pre></div>
</div>
]]></content:encoded></item><item><title>The regex | (or) operator splits the pattern left or right unless explicitly grouped</title><link>https://bjorn.now/til/2025-10-08-regex-or-operator-splits-pattern-left-or-right-unless-constrained/?utm_medium=feed</link><pubDate>Wed, 08 Oct 2025 23:34:03 +0200</pubDate><guid>https://bjorn.now/til/2025-10-08-regex-or-operator-splits-pattern-left-or-right-unless-constrained/</guid><description>The regex | (or) operator splits the pattern left or right unless explicitly grouped. Group it using (pattern) if you need to use the match later, or as a non-capturing group (?:pattern) if you do it for clarity.
I used to think there was some magic rule about how | decided where to split, but it’s simply: characters and subpatterns concatenate into a single pattern first, then | splits the entire thing left and right unless you explicitly group it. That’s because | has the lowest precedence so all other operations happen first.
So while I used to think that prefix_cat|dog would match prefix_cat and prefix_dog, it actually matches prefix_cat and then dog.
And that exact problem in a real-world example, to find if a page is linking to another, both relative or full URL:</description><content:encoded><![CDATA[<p>The regex <code>|</code> (or) operator splits the pattern left or right unless explicitly grouped.
Group it using <code>(pattern)</code> if you need to use the match later,
or as a non-capturing group <code>(?:pattern)</code> if you do it for clarity.</p>
<p>I used to think there was some magic rule about how <code>|</code> decided where to split,
but it&rsquo;s simply: characters and subpatterns concatenate into a single pattern first,
then <code>|</code> splits the entire thing left and right unless you explicitly group it.
That&rsquo;s because <code>|</code> <a href="https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04_08">has the lowest precedence</a> so <em>all other</em> operations happen first.</p>
<p>So while I used to think that <code>prefix_cat|dog</code> would match <code>prefix_cat</code> and <code>prefix_dog</code>,
it actually matches <code>prefix_cat</code> and then <code>dog</code>.</p>
<p>And that exact problem in a real-world example, to find if a page is linking to another, both relative or full URL:</p>
<p>The regex <code>&lt;a[^&gt;]+href=[&quot;']https://www.example.com/page|/page</code> will match <code>/page</code>,
just as in the <code>dog</code> example, it&rsquo;s splitting the whole pattern and isn&rsquo;t what I want.</p>
<p>See it in action with <a href="https://regex101.com/r/6RylLz/2">the excellent Regex101.com</a>:</p>
<figure><img src="/img/2025/10-regex-01.png"
    alt="The Regex101.com site showing the Regex in action and that it matches /page">
</figure>

<p>To make it do what I want I need to constrain it in a capturing group <code>(pattern)</code>,
and since I don&rsquo;t want to reference the group later,
I constrain it inside a non-capturing group <code>(?:pattern)</code>.</p>
<p>That gives me the pattern <code>&lt;a[^&gt;]+href=[&quot;'](?:https://www\.example\.com/page|/page)</code>,
and <a href="https://regex101.com/r/kgeGyp/3">then it matches</a> both the full URL (<a href="https://www.example.com/page">https://www.example.com/page</a>) and the relative URL (/page):</p>
<figure><img src="/img/2025/10-regex-02.png"
    alt="The Regex101.com site showing the Regex in action and that it matches both links per the pattern as I intended">
</figure>

<p>I love Regex101.com. 🙂
I have commented gnarly regexes in my code with links to it,
so colleagues can paste them there and understand what&rsquo;s going on.
The sidebar explains what each subpattern does,
and the color highlighting shows what matches what in real-time.</p>]]></content:encoded></item><item><title>Execute the data node and pin instead of rerunning the workflow to get fresh data in n8n</title><link>https://bjorn.now/til/2025-10-08-execute-the-data-node-and-pin-instead-of-rerunning-workflow-to-get-fresh-data-in-n8n/?utm_medium=feed</link><pubDate>Wed, 08 Oct 2025 16:23:39 +0200</pubDate><guid>https://bjorn.now/til/2025-10-08-execute-the-data-node-and-pin-instead-of-rerunning-workflow-to-get-fresh-data-in-n8n/</guid><description>Execute the data node/step and pin it, instead of rerunning the workflow or the step that relies on that data, when modifying a step that needs fresh data.
Example: When modifying Mastodon/Bluesky cross-posting nodes that depend on an RSS Feed Trigger, execute and pin the RSS node. Then iterate on the posting logic that only depends on the RSS data without triggering any posting.</description><content:encoded><![CDATA[<p>Execute the data node/step and pin it,
instead of rerunning the workflow or the step that relies on that data,
when modifying a step that needs fresh data.</p>
<figure><img src="/img/2025/10-n8n-step-rerun.png"
    alt="n8n workflow showing a sequential workflow starting with RSS fetching and going through Telegram, Mastodon, and Bluesky">
</figure>

<p>Example: When modifying Mastodon/Bluesky cross-posting nodes that depend on an RSS Feed Trigger,
execute and pin the RSS node.
Then iterate on the posting logic that <em>only depends</em> on the RSS data without triggering any posting.</p>
<p>Where my confusion came from: I haven&rsquo;t figured out fan-out and fan-in,
because I would expect to run my cross-posting in parallel and not sequentially,
but… I have seen there <em>is</em> some kind of fan-out, but how to use it? 🤷</p>
<p>So my assumption that I wouldn&rsquo;t get the data reloaded if I <strong>only</strong> ran the first step was incorrect,
and I&rsquo;ve been a bit hesitant to test since there&rsquo;s only production (this personal stuff doesn&rsquo;t have a proper testing env 😅).</p>]]></content:encoded></item><item><title>Hugo's resources.Get only finds files in assets/, not static/</title><link>https://bjorn.now/til/2025-10-08-hugos-resources-get-only-finds-files-in-assets-not-static/?utm_medium=feed</link><pubDate>Wed, 08 Oct 2025 15:14:34 +0200</pubDate><guid>https://bjorn.now/til/2025-10-08-hugos-resources-get-only-finds-files-in-assets-not-static/</guid><description>Hugo’s resources.Get only finds files in assets/, not static/.
I kept my assets/resources in static/ and didn’t get why I always had to write /absolute/urls and use hope as a strategy, when I moved them into assets/ I could look up all my assets.</description><content:encoded><![CDATA[<p>Hugo&rsquo;s <code>resources.Get</code> only finds files in <code>assets/</code>, not <code>static/</code>.</p>
<p>I kept my assets/resources in <code>static/</code> and didn&rsquo;t get why I always had to write <code>/absolute/urls</code> and use hope as a strategy,
when I moved them into <code>assets/</code> I could look up all my assets.</p>
<p>The below would fail because it couldn&rsquo;t find the asset:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plain" data-lang="plain"><span class="line"><span class="cl">{{ with resources.Get &#34;/author/bjorn.jpg&#34; }}
</span></span><span class="line"><span class="cl">  &lt;img src=&#34;{{ .RelPermalink }}&#34; alt=&#34;Björn Andersson&#34;&gt;
</span></span><span class="line"><span class="cl">{{ end }}</span></span></code></pre></div>
</div>
<p>Hugo has global assets accessed through <code>resources.Get</code> which <strong>only</strong> looks up in <code>assets/</code>.
Then page assets that you get through <code>.Page.Resources.Get</code> instead, which I haven&rsquo;t used yet.</p>
<p>If you want to use both, then do like the <a href="https://github.com/gohugoio/hugo/blob/4414ef73f3cd490caf93e0d50a6102db9ab28318/tpl/tplimpl/embedded/templates/_shortcodes/figure.html#L9C5-L11C16">figure shortcode</a> and do it in an or:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plain" data-lang="plain"><span class="line"><span class="cl">{{- with or (.Page.Resources.Get $u.Path) 
</span></span><span class="line"><span class="cl">            (resources.Get $u.Path) -}}
</span></span><span class="line"><span class="cl">{{- $src = .RelPermalink -}}
</span></span><span class="line"><span class="cl">{{- end -}}</span></span></code></pre></div>
</div>]]></content:encoded></item><item><title>Use usermod to rename users on Linux</title><link>https://bjorn.now/til/2025-10-01-use-usermod-to-rename-users-on-linux/?utm_medium=feed</link><pubDate>Wed, 01 Oct 2025 13:24:05 +0200</pubDate><guid>https://bjorn.now/til/2025-10-01-use-usermod-to-rename-users-on-linux/</guid><description>Use usermod -l newuser -d /home/newuser -m olduser when renaming a user account, because it’ll update all relevant system files and move the home folder in one go.
Then groupmod -n newgroup oldgroup if you need to rename the group.</description><content:encoded><![CDATA[<p>Use <code>usermod -l newuser -d /home/newuser -m olduser</code> when renaming a user account, because it&rsquo;ll update all relevant system files and move the home folder in one go.</p>
<p>Then <code>groupmod -n newgroup oldgroup</code> if you need to rename the group.</p>
<p>I had a vague memory of being able to change settings (shell etc.) by updating using <code>vipw</code> so I renamed there and then edited <code>/etc/groups</code> but I then got an error when trying to become that user:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># su bjornnow
</span></span><span class="line"><span class="cl">su: Authentication failure</span></span></code></pre></div>
</div>
<p>To fix it, I had to update all these files (and rename the home folder):</p>
<ul>
<li><code>vipw</code>: updates <code>/etc/passwd</code></li>
<li><code>vipw -s</code>: updates <code>/etc/shadow</code> which contains the password hashes and are only readable by root</li>
<li><code>vigr</code>: updates <code>/etc/group</code></li>
<li><code>vigr -s</code>: updates <code>/etc/gshadow</code> the same as passwd (also TIL, groups can have passwords)</li>
</ul>
<p>This is what you get when you think you remember how things work, and didn&rsquo;t want to look up how usermod worked, a little bit of knowledge is dangerous and all… 😅</p>]]></content:encoded></item><item><title>Use --resolve with curl when testing Caddy on localhost</title><link>https://bjorn.now/til/2025-10-01-use-resolve-with-curl-when-testing-caddy-on-localhost/?utm_medium=feed</link><pubDate>Wed, 01 Oct 2025 12:44:45 +0200</pubDate><guid>https://bjorn.now/til/2025-10-01-use-resolve-with-curl-when-testing-caddy-on-localhost/</guid><description>Use curl -k --resolve bjorn.now:443:127.0.0.1 https://bjorn.now when testing an HTTPS site locally using Caddy, instead of curl -k -H "Host: bjorn.now" https://localhost, because Caddy relies on SNI and refuses the connection if it doesn’t know which host you’re trying to connect to.</description><content:encoded><![CDATA[<p>Use <code>curl -k --resolve bjorn.now:443:127.0.0.1 https://bjorn.now</code> when testing an HTTPS site locally using <a href="https://caddyserver.com/">Caddy</a>, instead of <code>curl -k -H &quot;Host: bjorn.now&quot; https://localhost</code>, because Caddy relies on <a href="https://en.wikipedia.org/wiki/Server_Name_Indication">SNI</a> and refuses the connection if it doesn&rsquo;t know which host you&rsquo;re trying to connect to.</p>
<p>This seems to be a security decision to avoid leaking which certificates (and therefore sites) it hosts by not providing a default (can&rsquo;t find a source and it&rsquo;s what the search previews give me). Which my old nginx setup did.</p>
<p>The cryptic error message from cURL, which I didn&rsquo;t find anything obvious online about:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl: (35) OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error</span></span></code></pre></div>
</div>
<p>And I debugged this together with Claude by enabling debug logging in Caddy (<code>{ debug }</code> at the top level in the <code>Caddyfile</code>) which gave me this output when I called it using cURL:</p>
<div class="no-copy-button">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">2025/10/01 10:04:56.330 DEBUG   events  event   {&#34;name&#34;: &#34;tls_get_certificate&#34;, &#34;id&#34;: &#34;7353980b-a85e-4f10-9704-89da7358ef89&#34;, &#34;origin&#34;: &#34;tls&#34;, &#34;data&#34;: {&#34;client_hello&#34;:{&#34;CipherSuites&#34;:[4866,4867,4865,49196,49200,159,52393,52392,52394,49195,49199,158,49188,49192,107,49187,49191,103,49162,49172,57,49161,49171,51,157,156,61,60,53,47,255],&#34;ServerName&#34;:&#34;&#34;,&#34;SupportedCurves&#34;:[29,23,30,25,24,256,257,258,259,260],&#34;SupportedPoints&#34;:&#34;AAEC&#34;,&#34;SignatureSchemes&#34;:[1027,1283,1539,2055,2056,2057,2058,2059,2052,2053,2054,1025,1281,1537,771,769,770,1026,1282,1538],&#34;SupportedProtos&#34;:null,&#34;SupportedVersions&#34;:[772,771],&#34;Conn&#34;:{}}}}
</span></span><span class="line"><span class="cl">2025/10/01 10:04:56.330 DEBUG   tls.handshake   no matching certificates and no custom selection logic  {&#34;identifier&#34;: &#34;127.0.0.1&#34;}
</span></span><span class="line"><span class="cl">2025/10/01 10:04:56.331 DEBUG   tls.handshake   all external certificate managers yielded no certificates and no errors {&#34;remote_ip&#34;: &#34;127.0.0.1&#34;, &#34;remote_port&#34;: &#34;42516&#34;, &#34;sni&#34;: &#34;&#34;}
</span></span><span class="line"><span class="cl">2025/10/01 10:04:56.331 DEBUG   tls.handshake   no certificate matching TLS ClientHello {&#34;remote_ip&#34;: &#34;127.0.0.1&#34;, &#34;remote_port&#34;: &#34;42516&#34;, &#34;server_name&#34;: &#34;&#34;, &#34;remote&#34;: &#34;127.0.0.1:42516&#34;, &#34;identifier&#34;: &#34;127.0.0.1&#34;, &#34;cipher_suites&#34;: [4866, 4867, 4865, 49196, 49200, 159, 52393, 52392, 52394, 49195, 49199, 158, 49188, 49192, 107, 49187, 49191, 103, 49162, 49172, 57, 49161, 49171, 51, 157, 156, 61, 60, 53, 47, 255], &#34;cert_cache_fill&#34;: 0.0002, &#34;load_if_necessary&#34;: true, &#34;obtain_if_necessary&#34;: true, &#34;on_demand&#34;: false}
</span></span><span class="line"><span class="cl">2025/10/01 10:04:56.332 DEBUG   http.stdlib     http: TLS handshake error from 127.0.0.1:42516: no certificate available for &#39;127.0.0.1&#39;</span></span></code></pre></div>
</div>
<p>The culprit was the <code>&quot;ServerName&quot;:&quot;&quot;</code>, and I&rsquo;m guessing also <code>&quot;sni&quot;: &quot;&quot;</code>, so if you need to validate and see that as well then that&rsquo;s likely the problem. 🙂</p>
<hr>
<p>This jogs my memory, back in the olden times, we would need a unique IP address for each HTTPS site because we couldn&rsquo;t multi-host, and SNI solved that problem.</p>
<p>Importantly, SNI <em>doesn&rsquo;t</em> encrypt the host name and it&rsquo;s still sent in plaintext to the server.</p>]]></content:encoded></item><item><title>Use :focus-visible instead of :focus, because it will only show when helpful</title><link>https://bjorn.now/til/2025-09-30-focus-visible-instead-of-focus-to-avoid-extra-styling/?utm_medium=feed</link><pubDate>Tue, 30 Sep 2025 13:09:33 +0200</pubDate><guid>https://bjorn.now/til/2025-09-30-focus-visible-instead-of-focus-to-avoid-extra-styling/</guid><description>Use :focus-visible instead of :focus, because it will only show when helpful.
:focus-visible only shows focus styling when the browser determines keyboard navigation is being used, unlike :focus which triggers on all interactions including mouse clicks.</description><content:encoded><![CDATA[<p>Use <code>:focus-visible</code> instead of <code>:focus</code>, because it will <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible#focus_vs_focus-visible">only show when helpful.</a></p>
<p><code>:focus-visible</code> only shows focus styling when the browser determines keyboard navigation is being used, unlike <code>:focus</code> which triggers on all interactions including mouse clicks.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">button</span><span class="p">:</span><span class="nd">focus-visible</span> <span class="p">{</span> <span class="k">outline</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span> <span class="kc">solid</span> <span class="kc">blue</span><span class="p">;</span> <span class="p">}</span></span></span></code></pre></div>
</div>
<p>This prevents the &ldquo;clicked button stays highlighted&rdquo; problem while preserving accessibility for keyboard users. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible#focus_vs_focus-visible">Full browser logic on MDN</a>.</p>]]></content:encoded></item><item><title>Recycling bins are for packaging, not the products</title><link>https://bjorn.now/til/2025-09-23-recycling-bins-are-for-packaging/?utm_medium=feed</link><pubDate>Tue, 23 Sep 2025 11:43:42 +0200</pubDate><guid>https://bjorn.now/til/2025-09-23-recycling-bins-are-for-packaging/</guid><description>Recycling bins are for packaging, not the products themselves. Wooden ice cream stick? That’s “other,” it’s part of the product, at least in Karlstad. The wrapper? That goes in the appropriate recycling bin based on material.
This seems to be based on EU law, which makes sense, so I’ll assume the gist of this is true in other EU countries too.</description><content:encoded><![CDATA[<p>Recycling bins are for packaging, not the products themselves. Wooden ice cream stick? That&rsquo;s &ldquo;other,&rdquo; it&rsquo;s part of the product, at least <a href="https://karlstadsenergi.se/atervinning/sortera-ditt-avfall-ratt">in Karlstad</a>. The wrapper? That goes in the appropriate recycling bin based on material.</p>
<p>This seems to be based on EU law, which makes sense, so I&rsquo;ll assume the gist of this is true in other EU countries too.</p>
<p>This has to do with &ldquo;<a href="https://karlstadsenergi.se/atervinning/sortera-ditt-avfall-ratt/sortera-en-forpackning-eller-foremal">producentansvar</a>&rdquo; (&ldquo;producer responsibility&rdquo;) which by law requires producers of products to be responsible for the recycling of the packaging, by joining a &ldquo;collection system that takes operative and financial responsibility for collecting and managing packaging material.&quot;<a href="https://www.riksdagen.se/sv/dokument-och-lagar/dokument/svensk-forfattningssamling/forordning-20181462-om-producentansvar-for_sfs-2018-1462/">1 § 3</a></p>
<p>This sounds like systems thinking in play: By making producers responsible for recycling, they&rsquo;re incentivized to create packaging that&rsquo;s both easy to identify/recycle (cheaper for them) and easy for consumers to sort correctly (to hit reuse targets).</p>
<p>I watched Chris Spargo&rsquo;s video on <a href="https://www.youtube.com/watch?v=fuwaOiTHl7U">Why recycling in England is so complicated?</a> yesterday, which explains how England is going through this shift to Extended Producer Responsibility (EPR) right now, and without seeing that video it wouldn&rsquo;t have clicked as easy when I was reading this now.</p>
<p>(The egg shells don&rsquo;t qualify as food waste while bones do, I don&rsquo;t know what to make of that, and apparently it&rsquo;s the opposite in my mom&rsquo;s municipality 🤷)</p>]]></content:encoded></item><item><title>How to refresh Telegram link previews</title><link>https://bjorn.now/til/2025-09-17-refresh-telegram-link-previews/?utm_medium=feed</link><pubDate>Wed, 17 Sep 2025 12:02:24 +0200</pubDate><guid>https://bjorn.now/til/2025-09-17-refresh-telegram-link-previews/</guid><description>to refresh a link preview in Telegram open a chat with Webpage Bot and send the URL, for those cases where you fix a typo or update the image.</description><content:encoded>&lt;p>to refresh a link preview in Telegram open a chat with &lt;a href="https://telegram.me/webpagebot">Webpage Bot&lt;/a> and send the URL, for those cases where you fix a typo or update the image.&lt;/p>
</content:encoded></item><item><title>gum: clean user interactions for shell scripts</title><link>https://bjorn.now/til/2025-08-20-gum-is-a-cli-tool-that-helps-your-make-your-scripts-easy-to-interact-with/?utm_medium=feed</link><pubDate>Wed, 20 Aug 2025 23:02:03 +0800</pubDate><guid>https://bjorn.now/til/2025-08-20-gum-is-a-cli-tool-that-helps-your-make-your-scripts-easy-to-interact-with/</guid><description>gum simplifies making shell scripts interactive: no more wrestling with read commands and ANSI escape codes for user input: just proper text editing, defaults, and clean UI.
Their demo:</description><content:encoded><![CDATA[<p><a href="https://github.com/charmbracelet/gum">gum</a> simplifies making shell scripts interactive: no more wrestling with read commands and ANSI escape codes for user input: just proper text editing, defaults, and clean UI.</p>
<p><a href="https://github.com/charmbracelet/gum/blob/9ab722ca4fe1e248283091626bd9fe5ecea65e1b/examples/demo.sh">Their demo</a>: <figure><img src="/img/2025/gum-demo.gif"
    alt="gum gemo">
</figure>
</p>
<p>For example, if you run:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gum input --width <span class="m">50</span> --header <span class="s2">&#34;Favorite author?&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> --value <span class="s2">&#34;Terry Pratchett&#34;</span></span></span></code></pre></div>
</div>
<p>It will give you a question with a sensible default that you can then change as you want, and what you wrote as you hit <code>enter</code> will be returned so your script can then use it.</p>
<p>If you instead has some options where you want to select one:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gum choose --header <span class="s2">&#34;Favorite book?&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="s2">&#34;Hogfather&#34;</span> <span class="s2">&#34;Thief of Time&#34;</span> <span class="s2">&#34;The Night Watch&#34;</span> <span class="s2">&#34;Small Gods&#34;</span></span></span></code></pre></div>
</div>
<p>Which gives you a selection box, you can configure to allow multiple selections, and the one you pick is then returned.</p>
<p>And one I really love, have it show a spinner to indicate that yes… something is still going on:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gum spin --title<span class="o">=</span><span class="s2">&#34;Counting down from 10&#34;</span> sleep <span class="m">10</span></span></span></code></pre></div>
</div>
<p>Built on <a href="https://github.com/charmbracelet/bubbletea">bubbletea</a>, a Go TUI framework, use that directly if you need these components in Go code rather than shell scripts.</p>]]></content:encoded></item><item><title>Non-interactive shells will source the file pointed at by a variable at launch</title><link>https://bjorn.now/til/2025-08-15-if-you-start-a-non-interactive-bash-shell-it-will-source-the-content-of-the-file-defined-in-bash-env-and-env-for-a-posix-shell/?utm_medium=feed</link><pubDate>Fri, 15 Aug 2025 21:19:18 +0800</pubDate><guid>https://bjorn.now/til/2025-08-15-if-you-start-a-non-interactive-bash-shell-it-will-source-the-content-of-the-file-defined-in-bash-env-and-env-for-a-posix-shell/</guid><description>If you start a non-interactive bash shell it will source the content of the file defined in BASH_ENV (and ENV for a POSIX shell).</description><content:encoded><![CDATA[<p>If you start a non-interactive bash shell it will source the content of the file defined in <code>BASH_ENV</code> (and <code>ENV</code> for a POSIX shell).</p>
<blockquote>
<p>When bash is started non-interactively, to run a shell script, for example, it looks for the variable <code>BASH_ENV</code> in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute.  Bash behaves as if the following command were executed:</p>
<p><code>if [ -n &quot;$BASH_ENV&quot; ]; then . &quot;$BASH_ENV&quot;; fi</code></p>
<p>but the value of the <code>PATH</code> variable is not used to search for the file name.</p>
</blockquote>]]></content:encoded></item><item><title>Syntax for declaring and using YAML anchors</title><link>https://bjorn.now/til/2025-08-15-the-yaml-anchors-to-name-a-reusable-section-are-defined-by-name-and-then-to-use-them-to-replace-the-value-of-something-you-do-name/?utm_medium=feed</link><pubDate>Fri, 15 Aug 2025 21:19:18 +0800</pubDate><guid>https://bjorn.now/til/2025-08-15-the-yaml-anchors-to-name-a-reusable-section-are-defined-by-name-and-then-to-use-them-to-replace-the-value-of-something-you-do-name/</guid><description>The YAML anchors to name a reusable section are defined by &amp;name and then to use them to replace the value of something you do *name, if you want to “unsplat”/merge a dictionary/object then use &lt;&lt;: *name and then it’ll insert it at that point.</description><content:encoded><![CDATA[<p>The YAML anchors to name a reusable section are defined by <code>&amp;name</code> and then to use them to replace the value of something you do <code>*name</code>, if you want to &ldquo;unsplat&rdquo;/merge a dictionary/object then use <code>&lt;&lt;: *name</code> and then it&rsquo;ll insert it at that point.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">world</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;world</span><span class="w"> </span><span class="l">World</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">example</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;example-anchor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">HELLO</span><span class="p">:</span><span class="w"> </span><span class="cp">*world</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">There</span><span class="p">:</span><span class="w"> </span><span class="l">Yo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">my-values</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">&lt;&lt;</span><span class="p">:</span><span class="w"> </span><span class="cp">*example-anchor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">foo</span><span class="p">:</span><span class="w"> </span><span class="l">bar</span></span></span></code></pre></div>
</div>
<p>becomes</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">example</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">HELLO</span><span class="p">:</span><span class="w"> </span><span class="l">World</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">There</span><span class="p">:</span><span class="w"> </span><span class="l">Yo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">my-values</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">HELLO</span><span class="p">:</span><span class="w"> </span><span class="l">World</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">There</span><span class="p">:</span><span class="w"> </span><span class="l">Yo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">foo</span><span class="p">:</span><span class="w"> </span><span class="l">bar</span></span></span></code></pre></div>
</div>]]></content:encoded></item><item><title>YAML does *not* support unsplatting/merging lists</title><link>https://bjorn.now/til/2025-08-15-yaml-does-not-support-unsplatting-lists-and-thats-intentional-https/?utm_medium=feed</link><pubDate>Fri, 15 Aug 2025 21:19:18 +0800</pubDate><guid>https://bjorn.now/til/2025-08-15-yaml-does-not-support-unsplatting-lists-and-thats-intentional-https/</guid><description>YAML does not support unsplatting lists (basically, merging list items inline like you can with objects) and that’s intentional.</description><content:encoded><![CDATA[<p>YAML does not support unsplatting lists (basically, merging list items inline like you can with objects) and <a href="https://github.com/yaml/yaml/issues/35">that&rsquo;s intentional</a>.</p>
<p>So if you have a document like below, there is no syntax to make the <code>commands</code> a three item list:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">example</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;Hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;World!&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">&lt;&lt; *example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;Oho!&#34;</span></span></span></code></pre></div>
</div>
<p><strong>WILL NOT</strong> turn into:</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">example</span><span class="p">:</span><span class="w"> </span><span class="cp">&amp;example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;Hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;World!&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;Hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;World!&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;Oho!&#34;</span></span></span></code></pre></div>
</div>
<p>…and just because before I found the issue where it was described that this isn&rsquo;t happening <a href="https://github.com/gaqzi/test-go-yaml">I had made a test repo</a> to try and understand if it was a library/usage issue, because I noticed I could get things running on CI with <code>&lt;&lt; *example</code> which syntax didn&rsquo;t give syntax error, but also did <em>nothing</em> when running on Drone CI.</p>]]></content:encoded></item><item><title>/usr/bin/env handles commands with subcommands</title><link>https://bjorn.now/til/2025-08-13-usr-bin-env-with-subcommands/?utm_medium=feed</link><pubDate>Wed, 13 Aug 2025 08:21:23 +0800</pubDate><guid>https://bjorn.now/til/2025-08-13-usr-bin-env-with-subcommands/</guid><description>/usr/bin/env executes commands with flags/subcommands, not just bare executables. Which is great if you, for example, have a script/lint that’s a Python script, and it needs dependencies from a virtualenv that isn’t active when you call it.
Just put your shebang as /usr/bin/env uv run python3 and it always runs in the virtualenv, no wrapper script needed. This feels obvious in hindsight, it’s what you expect from these tools 😃</description><content:encoded><![CDATA[<p><code>/usr/bin/env</code> executes commands with flags/subcommands, not just bare executables. Which is great if you, for example, have a <code>script/lint</code> that&rsquo;s a Python script, and it needs dependencies from a virtualenv that isn&rsquo;t active when you call it.</p>
<p>Just put your shebang as <code>/usr/bin/env uv run python3</code> and it always runs in the virtualenv, no wrapper script needed. This feels obvious in hindsight, it&rsquo;s <em>what you expect</em> from these tools 😃</p>
]]></content:encoded></item><item><title>Drone CI handling of ${VAR} vs. $VAR</title><link>https://bjorn.now/til/2025-08-02-drone-ci-has-special-handling-for-var-and-will-replace-it-itself-instead-of-letting-the-shell-do-it/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-drone-ci-has-special-handling-for-var-and-will-replace-it-itself-instead-of-letting-the-shell-do-it/</guid><description>Drone CI has special handling for ${VAR} and will replace it itself instead of letting the shell do it. So if you have export PATH=${GOPATH}/bin:$PATH and have $GOPATH defined in your global environment key, then it’ll become a blank value. But if you do export PATH=$GOPATH/bin:$PATH it’ll work. You can escape by adding an extra dollar sign, i.e. $${VAR} will be evaluated by the shell.
This is different from normal shell where ${VAR} is the same as $VAR and it exists to make sure you can concatenate strings without issues, i.e.</description><content:encoded><![CDATA[<p>Drone CI has special handling for <code>${VAR}</code> and will replace it <strong>itself</strong> instead of letting the shell do it. So if you have <code>export PATH=${GOPATH}/bin:$PATH</code> and have <code>$GOPATH</code> defined in your global environment key, then it&rsquo;ll become a blank value. But if you do <code>export PATH=$GOPATH/bin:$PATH</code> it&rsquo;ll work. You <a href="https://laszlo.cloud/drone-environment-variables-three-tips#when-variables-resolve-to-empty-string">can escape by adding an extra dollar sign</a>, i.e. <code>$${VAR}</code> will be evaluated by the shell.</p>
<p>This is different from normal shell where <code>${VAR}</code> is the same as <code>$VAR</code> and it exists to make sure you can concatenate strings without issues, i.e.</p>
<div class=" copy-enabled">
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">VAR</span><span class="o">=</span>m000
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#39;</span><span class="si">${</span><span class="nv">VAR</span><span class="si">}</span><span class="s2">est&#39;&#34;</span>  <span class="c1"># =&gt; &#39;m000est&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;&#39;</span><span class="nv">$VARest</span><span class="s2">&#39;&#34;</span>    <span class="c1"># =&gt; &#39;&#39;</span></span></span></code></pre></div>
</div>
]]></content:encoded></item><item><title>Drone CI's DAG ignores skipped steps</title><link>https://bjorn.now/til/2025-08-02-drone-ci-will-not-smartly-skip-subsequent-steps-if-you-have-a-when-on-the-very-first-step-in-a-depends-on-chain/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-drone-ci-will-not-smartly-skip-subsequent-steps-if-you-have-a-when-on-the-very-first-step-in-a-depends-on-chain/</guid><description>Drone CI will not smartly skip subsequent steps if you have a when on the very first step in a depends_on chain, so you have to repeat the when condition for each step because it doesn’t realize that the first dependency is gone.</description><content:encoded><![CDATA[<p>Drone CI will <strong>not</strong> smartly skip subsequent steps if you have a <code>when</code> on the very first step in a <code>depends_on</code> chain, so you have to repeat the <code>when</code> condition for each step because it doesn&rsquo;t realize that the first dependency is gone.</p>
]]></content:encoded></item><item><title>Drone CI's when targets the merge target for PR events</title><link>https://bjorn.now/til/2025-08-02-drone-cis-when-for-deciding-in-which-cases-to-run-steps-pipelines-targets-https/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-drone-cis-when-for-deciding-in-which-cases-to-run-steps-pipelines-targets-https/</guid><description>Drone CI’s when for deciding in which cases to run steps/pipelines targets the merge target branch for PRs and not the actual PR’s, also it pulls from refs/pull/&lt;num&gt;/head instead of refs/heads/&lt;branch&gt; so you can’t target the branch itself using the pull_request event (use push on the pipeline and then branch for the step instead).</description><content:encoded><![CDATA[<p>Drone CI&rsquo;s <code>when</code> for deciding in which cases to run steps/pipelines <a href="https://docs.drone.io/pipeline/docker/syntax/conditions/#by-branch">targets</a> the <strong>merge target</strong> branch for PRs and not the actual PR&rsquo;s, also it pulls from <code>refs/pull/&lt;num&gt;/head</code> instead of <code>refs/heads/&lt;branch&gt;</code> so you can&rsquo;t target the branch itself using the <code>pull_request</code> event (use <code>push</code> on the pipeline and then <code>branch</code> for the step instead).</p>
]]></content:encoded></item><item><title>How "$@" vs $@ expands in shell</title><link>https://bjorn.now/til/2025-08-02-in-shell-scripts-if-you-do-it-will-actually-expand-correctly/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-in-shell-scripts-if-you-do-it-will-actually-expand-correctly/</guid><description>In shell scripts if you do "$@" it will actually expand “quoted sentences” correctly, and if you just do $@ it will always unwrap them into single words, I thought that if you did "$@" it would combine all arguments into a single argument, and what it does is do what I thought $@ alone did.
I.e., with "$@" the arguments "one two" three will be 2 arguments, the first being "one two", and without it will become three arguments, all separated by space.</description><content:encoded><![CDATA[<p>In shell scripts if you do <code>&quot;$@&quot;</code> it will actually expand &ldquo;quoted sentences&rdquo; correctly, and if you just do <code>$@</code> it will <strong>always</strong> unwrap them into single words, I thought that if you did <code>&quot;$@&quot;</code> it would combine all arguments into a single argument, and what it does is do what I thought <code>$@</code> alone did.</p>
<p>I.e., with <code>&quot;$@&quot;</code> the arguments <code>&quot;one two&quot; three</code> will be 2 arguments, the first being <code>&quot;one two&quot;</code>, and without it will become three arguments, all separated by space.</p>
]]></content:encoded></item><item><title>Link rel="canonical" is like a correlation ID</title><link>https://bjorn.now/til/2025-08-02-the-link-rel-href-is-a-bit-like-a-correlation-id-between-services-and-i-need-also-to-have-it-on-my-canonical-page/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-the-link-rel-href-is-a-bit-like-a-correlation-id-between-services-and-i-need-also-to-have-it-on-my-canonical-page/</guid><description>The &lt;link rel="canonical" href="…"&gt; is a bit like a correlation ID between services and I need also to have it on my canonical page, because if someone sends it with ?utm_source=foo it could be a different page than the one without the query string.</description><content:encoded><![CDATA[<p>The <code>&lt;link rel=&quot;canonical&quot; href=&quot;…&quot;&gt;</code> is a bit like a correlation ID between services and I need also to have it on my canonical page, because if someone sends it with <code>?utm_source=foo</code> it <strong>could</strong> be a different page than the one without the query string.</p>
]]></content:encoded></item><item><title>Pythong doesn't require typing for dict and list</title><link>https://bjorn.now/til/2025-08-02-python-doesnt-require-typing/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-python-doesnt-require-typing/</guid><description>Python doesn’t require typing.Dict and typing.List for those types since Python 3.9, you can just do dict[] and list[].</description><content:encoded><![CDATA[<p>Python doesn&rsquo;t require <code>typing.Dict</code> and <code>typing.List</code> for those types <a href="https://mypy.readthedocs.io/en/stable/builtin_types.html#generic-types">since Python 3.9</a>, you can just do <code>dict[]</code> and <code>list[]</code>.</p>
]]></content:encoded></item><item><title>Scripts/commands in a shell script inherits access to STDIN</title><link>https://bjorn.now/til/2025-08-02-the-scripts-inside-a-shell-script-inherit-access-to-stdin-when-you-call-them/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-the-scripts-inside-a-shell-script-inherit-access-to-stdin-when-you-call-them/</guid><description>The scripts/commands inside a shell script inherit access to STDIN when you call them, so if you have a shells script that only has cat and you do ./script.sh &lt; script.sh then it’ll output the content of itself</description><content:encoded><![CDATA[<p>The scripts/commands inside a shell script inherit access to STDIN when you call them, so if you have a shells script that only has <code>cat</code> and you do <code>./script.sh &lt; script.sh</code> then it&rsquo;ll output the content of itself</p>
]]></content:encoded></item><item><title>You can view the webhooks that GitHub sends in your project settings</title><link>https://bjorn.now/til/2025-08-02-you-can-view-the-webhooks-that-github-sends-in-your-project-settings/?utm_medium=feed</link><pubDate>Sat, 02 Aug 2025 09:37:29 +0800</pubDate><guid>https://bjorn.now/til/2025-08-02-you-can-view-the-webhooks-that-github-sends-in-your-project-settings/</guid><description>You can view the webhooks that GitHub sends in your project settings, so for example Drone CI getting notified and exactly what information that goes into the payload, and when/if it was sent, is available there.
Go to Settings -&gt; Webhooks and then click on the integration you want to look at.</description><content:encoded><![CDATA[<p>You can view the webhooks that GitHub sends in your project settings, so for example Drone CI getting notified and exactly what information that goes into the payload, and when/if it was sent, is available there.</p>
<p>Go to <code>Settings</code> -&gt; <code>Webhooks</code> and then click on the integration you want to look at.</p>
]]></content:encoded></item><item><title>Enabling macOS fingerprint reader for sudo</title><link>https://bjorn.now/til/2025-08-01-macos-fingerprint-reader-sudo/?utm_medium=feed</link><pubDate>Fri, 01 Aug 2025 23:15:00 +0800</pubDate><guid>https://bjorn.now/til/2025-08-01-macos-fingerprint-reader-sudo/</guid><description>You can enable the fingerprint reader for sudo on macOS, and pressing my finger on a button beats having to type the password, steps:
cp /etc/pam.d/sudo_local{.template,} Edit /etc/pam.d/sudo_local and uncomment auth sufficient pam_tid.so. The reason for doing this in sudo_local is that this file will not get reset with system changes from Apple.</description><content:encoded><![CDATA[<p>You can enable the fingerprint reader for sudo on macOS,
and pressing my finger on a button beats having to type the password, steps:</p>
<ol>
<li><code>cp /etc/pam.d/sudo_local{.template,}</code></li>
<li>Edit <code>/etc/pam.d/sudo_local</code> and uncomment <code>auth       sufficient     pam_tid.so</code>.</li>
</ol>
<p>The reason for doing this in <code>sudo_local</code> is that this file will not get reset with system changes from Apple.</p>
]]></content:encoded></item></channel></rss>