<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GitHub Actions on Not That Kind of Agent</title><link>https://agentolivia.github.io/tags/github-actions/</link><description>Recent content in GitHub Actions on Not That Kind of Agent</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 13 May 2026 00:00:00 -0700</lastBuildDate><atom:link href="https://agentolivia.github.io/tags/github-actions/index.xml" rel="self" type="application/rss+xml"/><item><title>Hardening GitHub Actions workflows against supply chain attacks</title><link>https://agentolivia.github.io/p/hardening-github-actions-workflows-against-supply-chain-attacks/</link><pubDate>Wed, 13 May 2026 00:00:00 -0700</pubDate><guid>https://agentolivia.github.io/p/hardening-github-actions-workflows-against-supply-chain-attacks/</guid><description>&lt;p&gt;I asked Claude Code to review the GitHub Actions workflows in this repo for supply chain attack risk. This post covers what it found, what we changed, and a prompt you can reuse on your own workflows.&lt;/p&gt;
&lt;h2 id="the-threat-model"&gt;The threat model
&lt;/h2&gt;&lt;p&gt;A supply chain attack via GitHub Actions works like this: a third-party action you depend on (say, &lt;code&gt;some-org/some-action@v2&lt;/code&gt;) gets its tag force-pushed with malicious code. On your next workflow run, that code executes in your CI environment. It has access to your secrets and token permissions.&lt;/p&gt;
&lt;p&gt;The same risk applies to npm. If a package in your &lt;code&gt;node_modules&lt;/code&gt; tree is compromised, its postinstall script runs during &lt;code&gt;npm ci&lt;/code&gt;. It can read any credentials in the job environment.&lt;/p&gt;
&lt;p&gt;Neither is theoretical. Both attack types have hit real CI pipelines.&lt;/p&gt;
&lt;h2 id="what-i-changed"&gt;What I changed
&lt;/h2&gt;&lt;h3 id="1-pin-third-party-actions-to-commit-shas"&gt;1. Pin third-party actions to commit SHAs
&lt;/h3&gt;&lt;p&gt;GitHub&amp;rsquo;s version tags (e.g., &lt;code&gt;@v2&lt;/code&gt;, &lt;code&gt;@v8&lt;/code&gt;) are mutable. A tag can be moved to point to a different commit without notice. Pinning to a commit SHA means the code you reviewed is the code that runs, forever.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Immutable vs mutable&lt;/summary&gt;
&lt;p&gt;Git tags are just labels. The &lt;code&gt;v8&lt;/code&gt; tag is a pointer, and whoever controls the repo can move it to a different commit. Same name, different code. That&amp;rsquo;s what mutable means here.&lt;/p&gt;
&lt;p&gt;A commit SHA is different. Git derives it from the content of the commit, so it can only refer to that one thing. You can&amp;rsquo;t redirect it.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what makes SHA pinning an actual security control. Once you&amp;rsquo;ve reviewed what&amp;rsquo;s at a given SHA, nobody can swap something else in without you noticing.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;I was using two third-party actions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peter-evans/create-pull-request@v8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;errata-ai/vale-action@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# After&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;# v8&lt;/code&gt; comment keeps it human-readable. To get the SHA for a tag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gh api repos/peter-evans/create-pull-request/git/ref/tags/v8 --jq &lt;span class="s1"&gt;&amp;#39;.object.sha&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;GitHub&amp;rsquo;s own first-party &lt;code&gt;actions/*&lt;/code&gt; are lower risk, but if your threat model requires it, the same pinning applies.&lt;/p&gt;
&lt;h3 id="2-add-explicit-permissions-blocks"&gt;2. Add explicit &lt;code&gt;permissions&lt;/code&gt; blocks
&lt;/h3&gt;&lt;p&gt;Without an explicit &lt;code&gt;permissions&lt;/code&gt; block, a workflow job inherits the repository&amp;rsquo;s default token permissions, which can be &lt;code&gt;read/write&lt;/code&gt; depending on repo settings. Declaring minimum permissions limits what an attacker can do with a compromised token. This is the security principle of &amp;ldquo;least privilege.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Three of my workflow files had no &lt;code&gt;permissions&lt;/code&gt; block at all:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Added to lint.yml, security.yml, vale.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;deploy.yml&lt;/code&gt; and &lt;code&gt;update-theme.yml&lt;/code&gt; workflows were already scoped correctly. &lt;code&gt;deploy.yml&lt;/code&gt; needs &lt;code&gt;pages: write&lt;/code&gt; and &lt;code&gt;id-token: write&lt;/code&gt; for GitHub Pages deployment. &lt;code&gt;update-theme.yml&lt;/code&gt; needs &lt;code&gt;contents: write&lt;/code&gt; and &lt;code&gt;pull-requests: write&lt;/code&gt; to create automated PRs.&lt;/p&gt;
&lt;h3 id="3-set-persist-credentials-false-on-checkouts-that-dont-need-to-push"&gt;3. Set &lt;code&gt;persist-credentials: false&lt;/code&gt; on checkouts that don&amp;rsquo;t need to push
&lt;/h3&gt;&lt;p&gt;By default, &lt;code&gt;actions/checkout&lt;/code&gt; writes the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; into &lt;code&gt;.git/config&lt;/code&gt; where it stays accessible to every subsequent step in the job. That includes &lt;code&gt;npm ci&lt;/code&gt;, which runs third-party postinstall scripts.&lt;/p&gt;
&lt;p&gt;For jobs that check out code but never push (linting, security scanning, prose review), there&amp;rsquo;s no reason for the credential to persist:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I applied this to &lt;code&gt;lint.yml&lt;/code&gt;, &lt;code&gt;security.yml&lt;/code&gt;, and &lt;code&gt;vale.yml&lt;/code&gt;. The &lt;code&gt;deploy.yml&lt;/code&gt; and &lt;code&gt;update-theme.yml&lt;/code&gt; workflows keep the default because they run git operations that need authentication.&lt;/p&gt;
&lt;h3 id="4-add-timeout-minutes-to-every-job"&gt;4. Add &lt;code&gt;timeout-minutes&lt;/code&gt; to every job
&lt;/h3&gt;&lt;p&gt;Jobs without a timeout run for up to 6 hours by default. That&amp;rsquo;s a long window of live token exposure if something goes wrong. Tight timeouts also limit the damage from a hung or hijacked job.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Each of these jobs completes in seconds, with the build and deploy pipeline taking under two minutes. I set all jobs to 5 minutes. That&amp;rsquo;s a reasonable buffer given actual run times, and a lot better than leaving the 6-hour default in place.&lt;/p&gt;
&lt;h2 id="the-prompt"&gt;The prompt
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the prompt I used. It works well with Claude Code in a repo with workflow files present:&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Review the GitHub Actions workflows in &lt;code&gt;.github/workflows/&lt;/code&gt; for supply chain attack risk. For each workflow file, check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Action pinning&lt;/strong&gt; — Are any third-party actions (non-&lt;code&gt;actions/*&lt;/code&gt;) pinned to a mutable version tag instead of a commit SHA? If so, fetch the current SHA for each tag using &lt;code&gt;gh api repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/git/ref/tags/&amp;lt;tag&amp;gt;&lt;/code&gt; and pin them. Add the version tag as a comment for readability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions&lt;/strong&gt; — Does every job have an explicit &lt;code&gt;permissions&lt;/code&gt; block declaring the minimum required scopes? If not, add one. Jobs that only read code should use &lt;code&gt;permissions: contents: read&lt;/code&gt;. Note which jobs legitimately need write scopes and why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;persist-credentials&lt;/code&gt;&lt;/strong&gt; — Does &lt;code&gt;actions/checkout&lt;/code&gt; default to persisting the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; into &lt;code&gt;.git/config&lt;/code&gt;? For any job that does not need to &lt;code&gt;git push&lt;/code&gt;, add &lt;code&gt;persist-credentials: false&lt;/code&gt; to the checkout step. This prevents the token from being readable by npm postinstall scripts or other downstream steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeouts&lt;/strong&gt; — Does every job have a &lt;code&gt;timeout-minutes&lt;/code&gt; value set? If not, add one. Base the value on a realistic upper bound for that job&amp;rsquo;s expected runtime.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For each finding, explain the risk and make the change. Do not pin GitHub&amp;rsquo;s own first-party &lt;code&gt;actions/*&lt;/code&gt; actions unless I ask — focus on third-party dependencies.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Run this against a repo with a clean working tree. The diff lands in one place, which makes it easier to spot anything unexpected before you commit.&lt;/p&gt;
&lt;h2 id="what-this-doesnt-cover"&gt;What this doesn&amp;rsquo;t cover
&lt;/h2&gt;&lt;p&gt;This review focuses on the workflow files themselves. It doesn&amp;rsquo;t cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Repository secrets&lt;/strong&gt; — if you have a personal access token (PAT) with broad org-wide scope stored as a repo secret, that&amp;rsquo;s a higher-impact risk than anything in the workflow YAML. Audit your repo and org secrets separately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pull_request_target&lt;/code&gt;&lt;/strong&gt; — workflows using this trigger run with write permissions even for PRs from forks, which can give fork contributors unintended write access to your repo. None of my workflows use it, but it&amp;rsquo;s worth checking yours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependabot for Actions&lt;/strong&gt; — automatically opens PRs to bump action versions. Pairing it with SHA pinning requires extra config but keeps pinned SHAs from going stale.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fixing error: GitHub Actions isn't allowed to open pull requests</title><link>https://agentolivia.github.io/p/fixing-error-github-actions-isnt-allowed-to-open-pull-requests/</link><pubDate>Wed, 06 May 2026 00:00:00 -0700</pubDate><guid>https://agentolivia.github.io/p/fixing-error-github-actions-isnt-allowed-to-open-pull-requests/</guid><description>&lt;p&gt;In &lt;a class="link" href="https://agentolivia.github.io/post/hugo-version-mismatch-automated-theme-updates/" &gt;my last post&lt;/a&gt;, I updated the &lt;code&gt;update-theme&lt;/code&gt; workflow to open a pull request instead of committing theme updates directly to &lt;code&gt;main&lt;/code&gt;. Smart in theory. In practice, when the workflow needed to open a pull request to bump the &lt;code&gt;hugo&lt;/code&gt; version, it failed. The workflow run also warned me about Node 20 actions being deprecated.&lt;/p&gt;
&lt;p&gt;I used Claude Code to help diagnose and work through both issues. This post is my note-to-future-self on what needed to change and where.&lt;/p&gt;
&lt;h2 id="problem-1-github-actions-isnt-allowed-to-open-pull-requests"&gt;Problem 1: GitHub Actions isn&amp;rsquo;t allowed to open pull requests
&lt;/h2&gt;&lt;p&gt;The workflow failed with this error from the GitHub REST API:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;GitHub Actions is not permitted to create or approve pull requests.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;peter-evans/create-pull-request&lt;/code&gt; action uses the built-in &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; to open PRs. The workflow already had the right permissions declared:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull-requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;But there are &lt;strong&gt;two separate settings&lt;/strong&gt; in the repository that also need to be enabled, and both were off by default.&lt;/p&gt;
&lt;h3 id="setting-1-actions-allowlist"&gt;Setting 1: Actions allowlist
&lt;/h3&gt;&lt;p&gt;Go to &lt;strong&gt;Settings → Actions → General&lt;/strong&gt; and look at the &amp;ldquo;Actions permissions&amp;rdquo; section. If you&amp;rsquo;re using the &amp;ldquo;Allow [org], and select non-[org], actions and reusable workflows&amp;rdquo; option, you need to explicitly list the third-party actions your workflows use in the allowlist field.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I added:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;peaceiris/actions-hugo@*,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;peter-evans/create-pull-request@*,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;errata-ai/vale-action@*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;@*&lt;/code&gt; wildcard covers any version tag, so this doesn&amp;rsquo;t need updating when action versions get bumped. GitHub&amp;rsquo;s own actions (&lt;code&gt;actions/checkout&lt;/code&gt;, &lt;code&gt;actions/setup-node&lt;/code&gt;, etc.) are covered separately by checking &lt;strong&gt;&amp;ldquo;Allow actions created by GitHub&amp;rdquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="setting-2-workflow-permissions"&gt;Setting 2: Workflow permissions
&lt;/h3&gt;&lt;p&gt;Further down on the same &lt;strong&gt;Settings → Actions → General&lt;/strong&gt; page is the &amp;ldquo;Workflow permissions&amp;rdquo; section. There&amp;rsquo;s a checkbox labeled &lt;strong&gt;&amp;ldquo;Allow GitHub Actions to create and approve pull requests&amp;rdquo;&lt;/strong&gt; that is unchecked by default. Check it.&lt;/p&gt;
&lt;p&gt;This is a separate gate from the &lt;code&gt;permissions:&lt;/code&gt; block in the workflow YAML. Even with &lt;code&gt;pull-requests: write&lt;/code&gt; in the workflow, the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; cannot open PRs unless this repo-level setting is also enabled.&lt;/p&gt;
&lt;p&gt;After enabling both, the workflow ran successfully and opened its first PR.&lt;/p&gt;
&lt;h2 id="problem-2-nodejs-20-deprecation-warnings"&gt;Problem 2: Node.js 20 deprecation warnings
&lt;/h2&gt;&lt;p&gt;With the workflow actually running, a new batch of warnings appeared:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4, peaceiris/actions-hugo@v3, peter-evans/create-pull-request@v7. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Node.js 20 reached end-of-life in April 2026. GitHub Actions is forcing a cutover to Node.js 24 on June 2nd, and removing Node.js 20 from runners entirely on September 16th.&lt;/p&gt;
&lt;p&gt;This affected every workflow in the repo, not just &lt;code&gt;update-theme&lt;/code&gt;. Claude Code helped me audit all five workflows and figure out what could be upgraded versus what needed a workaround.&lt;/p&gt;
&lt;h3 id="action-version-bumps"&gt;Action version bumps
&lt;/h3&gt;&lt;p&gt;Several actions had new major versions available with Node.js 24 support.&lt;/p&gt;
&lt;p&gt;Cross-workflow updates (applied to all five workflow files where used):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Action&lt;/th&gt;
 &lt;th&gt;Before&lt;/th&gt;
 &lt;th&gt;After&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/checkout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v4 / v5&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v6&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/setup-node&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v4&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v6&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;peter-evans/create-pull-request&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v7&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v8&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;deploy.yml&lt;/code&gt;-specific updates:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Action&lt;/th&gt;
 &lt;th&gt;Before&lt;/th&gt;
 &lt;th&gt;After&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/setup-go&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v5&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v6&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/configure-pages&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v5&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v6&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/cache&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v4&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v5&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/upload-pages-artifact&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v3&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v5&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;actions/deploy-pages&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;v4&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;v5&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="actions-that-dont-have-a-nodejs-24-release-yet"&gt;Actions that don&amp;rsquo;t have a Node.js 24 release yet
&lt;/h3&gt;&lt;p&gt;One action was already at its latest major version with no Node.js 24-compatible release available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;errata-ai/vale-action@v2&lt;/code&gt; (used in &lt;code&gt;vale.yml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For it, I added the opt-in environment variable to the affected job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FORCE_JAVASCRIPT_ACTIONS_TO_NODE24&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This tells the runner to use Node.js 24 for that action now, before GitHub forces it on June 2nd. If it breaks, that&amp;rsquo;s a signal to find an alternative or wait for an upstream update.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;peaceiris/actions-hugo@v3&lt;/code&gt; was in the same situation — no Node.js 24 release, same warning — but I replaced it entirely rather than applying the workaround. See below.&lt;/p&gt;
&lt;h3 id="replacing-peaceirisactions-hugo"&gt;Replacing peaceiris/actions-hugo
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;peaceiris/actions-hugo@v3&lt;/code&gt; is an unmaintained community wrapper that downloads Hugo from GitHub releases and puts it on PATH. Since it has no Node.js 24-native release and is just a thin wrapper around a direct download, the right move was to remove the dependency and do the download directly — the same pattern already used for Dart Sass in &lt;code&gt;deploy.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;deploy.yml&lt;/code&gt;, where the Hugo version is pinned in an environment variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p &amp;#34;${HOME}/.local/bin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -sLo &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -sLo &amp;#34;hugo_checksums.txt&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sha256sum --check --ignore-missing &amp;#34;hugo_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; tar -C &amp;#34;${HOME}/.local/bin&amp;#34; -xf &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; hugo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rm &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; &amp;#34;hugo_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;${HOME}/.local/bin&amp;#34; &amp;gt;&amp;gt; &amp;#34;${GITHUB_PATH}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In &lt;code&gt;update-theme.yml&lt;/code&gt;, where the workflow always wants the latest Hugo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.token }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; HUGO_VERSION=$(gh release view --repo gohugoio/hugo --json tagName --jq &amp;#39;.tagName | ltrimstr(&amp;#34;v&amp;#34;)&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p &amp;#34;${HOME}/.local/bin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -sLo &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -sLo &amp;#34;hugo_checksums.txt&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sha256sum --check --ignore-missing &amp;#34;hugo_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; tar -C &amp;#34;${HOME}/.local/bin&amp;#34; -xf &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; hugo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rm &amp;#34;hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&amp;#34; &amp;#34;hugo_checksums.txt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;${HOME}/.local/bin&amp;#34; &amp;gt;&amp;gt; &amp;#34;${GITHUB_PATH}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;sha256sum --check --ignore-missing&lt;/code&gt; step verifies the tarball against Hugo&amp;rsquo;s published checksums file before extracting. &lt;code&gt;--ignore-missing&lt;/code&gt; is needed because the checksums file covers all platforms and editions; we only downloaded one of them. This is something &lt;code&gt;peaceiris/actions-hugo&lt;/code&gt; never did, so the replacement is actually a supply chain improvement over what it replaced.&lt;/p&gt;
&lt;h3 id="aligning-the-pinned-nodejs-version"&gt;Aligning the pinned Node.js version
&lt;/h3&gt;&lt;p&gt;While auditing the workflows, I noticed the pinned &lt;code&gt;node-version&lt;/code&gt; values were inconsistent:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Workflow&lt;/th&gt;
 &lt;th&gt;Node version (before)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;24.12.0&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;lint.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;22&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;security.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Node.js 22 is now in Maintenance LTS (its Active LTS phase ended October 2025). Node.js 24 is the current Active LTS and what &lt;code&gt;deploy.yml&lt;/code&gt; was already using. I updated &lt;code&gt;lint.yml&lt;/code&gt; and &lt;code&gt;security.yml&lt;/code&gt; to &lt;code&gt;'24'&lt;/code&gt; so all workflows are consistent.&lt;/p&gt;
&lt;h2 id="the-two-things-to-remember"&gt;The two things to remember
&lt;/h2&gt;&lt;p&gt;If you set up a GitHub Actions workflow that opens PRs and it fails with a permissions error, check both places:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Settings → Actions → General → Actions permissions&lt;/strong&gt; — add third-party actions to the allowlist&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settings → Actions → General → Workflow permissions&lt;/strong&gt; — enable &amp;ldquo;Allow GitHub Actions to create and approve pull requests&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;permissions:&lt;/code&gt; block in the workflow YAML is necessary but not sufficient on its own. You need the repo-level checkbox too. Future me: check there first.&lt;/p&gt;</description></item><item><title>When Your Theme Auto-Updates But Your Build Environment Doesn't</title><link>https://agentolivia.github.io/p/when-your-theme-auto-updates-but-your-build-environment-doesnt/</link><pubDate>Wed, 29 Apr 2026 00:00:00 -0700</pubDate><guid>https://agentolivia.github.io/p/when-your-theme-auto-updates-but-your-build-environment-doesnt/</guid><description>&lt;p&gt;While poking around my &lt;a class="link" href="https://dashboard.tugboatqa.com/" target="_blank" rel="noopener"
 &gt;Tugboat Dashboard&lt;/a&gt;, I noticed the base preview for the main branch in this repo had failed. The site was still working, and it wasn’t until today that I needed to get pull request previews up and running again for this repo. The error was a bit cryptic at first glance, but it turned out to be a straightforward version mismatch with a less-than-straightforward paper trail.&lt;/p&gt;
&lt;p&gt;I used Claude Code in VS Code to help me diagnose and fix the error, and we worked together to devise a plan to update the update-theme workflow and Tugboat to be more &lt;strong&gt;resilient to automatic changes and to put me—the human—in the loop when needed&lt;/strong&gt;. Claude Code helped me write a detailed PR description and turn this incident into a blog post, which I edited and added to. Robot teamwork!&lt;/p&gt;
&lt;h2 id="the-error"&gt;The error
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WARN Module &lt;span class="s2"&gt;&amp;#34;github.com/CaiJimmy/hugo-theme-stack/v4&amp;#34;&lt;/span&gt; is not compatible
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with this Hugo version: Min 0.157.0 extended
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR error building site: ... at &amp;lt;reflect&amp;gt;: can&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t evaluate field
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IsImageResourceWithMeta in &lt;span class="nb"&gt;type&lt;/span&gt; interface &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Two errors, one cause. The theme (&lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
 &gt;hugo-theme-stack&lt;/a&gt;) had been auto-updated to a version that requires Hugo &lt;strong&gt;0.157.0 extended&lt;/strong&gt; as a minimum. My Tugboat config was pinned to &lt;strong&gt;Hugo 0.155.3&lt;/strong&gt;. The &lt;code&gt;IsImageResourceWithMeta&lt;/code&gt; template error isn&amp;rsquo;t a separate bug—it&amp;rsquo;s what happens when the theme tries to use a field that doesn&amp;rsquo;t exist in the older Hugo version.&lt;/p&gt;
&lt;h2 id="how-did-this-happen"&gt;How did this happen?
&lt;/h2&gt;&lt;p&gt;This site&amp;rsquo;s repo includes a GitHub Actions workflow, &lt;code&gt;.github/workflows/update-theme.yml&lt;/code&gt;, that runs on a daily cron schedule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo mod get -u&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Tidy go.mod, go.sum&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo mod tidy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Commit changes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;stefanzweifel/git-auto-commit-action@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit_message: &amp;#39;CI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That workflow installs &lt;code&gt;hugo-version: 'latest'&lt;/code&gt; and then runs &lt;code&gt;hugo mod get -u&lt;/code&gt;, which pulls the latest version of the theme and commits the result directly to &lt;code&gt;main&lt;/code&gt;. &lt;strong&gt;No build validation, no review step.&lt;/strong&gt; When the theme bumped its minimum Hugo requirement, the workflow committed the update anyway, the &lt;code&gt;main&lt;/code&gt; branch now had an incompatible theme version, and Tugboat&amp;rsquo;s next preview build failed.&lt;/p&gt;
&lt;p&gt;The version mismatch lived in two different config files that had no awareness of each other:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;File&lt;/th&gt;
 &lt;th&gt;Hugo version&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.github/workflows/update-theme.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;latest&lt;/code&gt; (always current)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.tugboat/config.yml&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.155.3&lt;/code&gt; (manually pinned, stale)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="the-immediate-fix"&gt;The immediate fix
&lt;/h2&gt;&lt;p&gt;The quickest fix was updating &lt;code&gt;.tugboat/config.yml&lt;/code&gt; to install a Hugo version that satisfies the theme&amp;rsquo;s requirements. Hugo 0.161.1 is the current latest release, so I bumped the download URL there:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="l"&gt;curl -Ls https://github.com/gohugoio/hugo/releases/download/v0.155.3/hugo_extended_0.155.3_Linux-64bit.tar.gz | tar -C /usr/local/bin -zxf - hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# After&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="l"&gt;curl -Ls https://github.com/gohugoio/hugo/releases/download/v0.161.1/hugo_extended_0.161.1_Linux-64bit.tar.gz | tar -C /usr/local/bin -zxf - hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That unblocks Tugboat, but it doesn&amp;rsquo;t prevent the same thing from happening the next time the theme bumps its minimum Hugo requirement. I needed to rethink the workflow.&lt;/p&gt;
&lt;h2 id="making-it-future-proof"&gt;Making it future-proof
&lt;/h2&gt;&lt;p&gt;The core problem is that the workflow had no feedback loop: it updated the theme, assumed everything was fine, and committed. I made three changes to address that.&lt;/p&gt;
&lt;h3 id="1-auto-sync-the-tugboat-hugo-version-automated"&gt;1. Auto-sync the Tugboat Hugo version (automated)
&lt;/h3&gt;&lt;p&gt;The workflow already installs the latest Hugo to run &lt;code&gt;hugo mod get -u&lt;/code&gt;. After updating the theme, I extract that version number and rewrite the download URL in &lt;code&gt;.tugboat/config.yml&lt;/code&gt; to match:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Sync Hugo version in Tugboat config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; HUGO_VERSION=$(hugo version | grep -oE &amp;#39;[0-9]+\.[0-9]+\.[0-9]+&amp;#39; | head -1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sed -i -E &amp;#34;s|/download/v[0-9.]+/hugo_extended_[0-9.]+_Linux-64bit|/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit|g&amp;#34; .tugboat/config.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;What does that command do?&lt;/summary&gt;
&lt;p&gt;The two lines work together to extract the installed Hugo version and rewrite the download URL in &lt;code&gt;.tugboat/config.yml&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="line-1--extract-the-version-number"&gt;Line 1 — extract the version number
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;hugo version&lt;/code&gt; outputs something like &lt;code&gt;hugo v0.161.1+extended linux/amd64 ...&lt;/code&gt;. The pipeline extracts just &lt;code&gt;0.161.1&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grep -oE '[0-9]+\.[0-9]+\.[0-9]+'&lt;/code&gt; — &lt;code&gt;-o&lt;/code&gt; prints only the matching text (not the whole line); &lt;code&gt;-E&lt;/code&gt; enables extended regex; the pattern matches any &lt;code&gt;digits.digits.digits&lt;/code&gt; sequence&lt;/li&gt;
&lt;li&gt;&lt;code&gt;| head -1&lt;/code&gt; — takes the first match, in case the output ever contains more than one version-like string&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is stored in &lt;code&gt;HUGO_VERSION&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="line-2--rewrite-the-url-in-tugboatconfigyml"&gt;Line 2 — rewrite the URL in &lt;code&gt;.tugboat/config.yml&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;sed -i -E &amp;quot;s|old|new|g&amp;quot;&lt;/code&gt; is a find-and-replace on the file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i&lt;/code&gt; — edit in place (modifies the file directly, no output)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-E&lt;/code&gt; — enables extended regex, needed for &lt;code&gt;+&lt;/code&gt; to mean &amp;ldquo;one or more&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s|...|...|g&lt;/code&gt; — the substitute command, using &lt;code&gt;|&lt;/code&gt; as the delimiter instead of the usual &lt;code&gt;/&lt;/code&gt; because the pattern itself contains forward slashes, which would otherwise need escaping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern matches the version-specific portion of the Hugo download URL—&lt;code&gt;[0-9.]+&lt;/code&gt; matches any version number like &lt;code&gt;0.155.3&lt;/code&gt; or &lt;code&gt;0.161.1&lt;/code&gt;. The replacement plugs &lt;code&gt;$HUGO_VERSION&lt;/code&gt; into both spots where the version number appears. The &lt;code&gt;g&lt;/code&gt; flag replaces all occurrences, though there&amp;rsquo;s only one matching line in the file.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Now the workflow keeps both environments in lock-step automatically. Whatever Hugo version the workflow installs and validates against is the same version Tugboat gets in the same commit.&lt;/p&gt;
&lt;h3 id="2-build-validation-automated"&gt;2. Build validation (automated)
&lt;/h3&gt;&lt;p&gt;After updating the theme and syncing the Tugboat config, the workflow now tries to actually build the site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build site&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo --gc --minify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the build fails—bad template, incompatible theme change, anything—the workflow stops here. Nothing gets committed. &lt;strong&gt;This is the automated gate: it catches functional breakage before it touches the repo.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="3-open-a-pr-instead-of-pushing-directly-to-main-human-in-the-loop"&gt;3. Open a PR instead of pushing directly to &lt;code&gt;main&lt;/code&gt; (human in the loop)
&lt;/h3&gt;&lt;p&gt;Even if the build succeeds, &lt;code&gt;hugo mod get -u&lt;/code&gt; is pulling in external code and committing it automatically. A theme update that builds cleanly could still introduce changes worth reviewing—a new JavaScript dependency, a layout change, a modified partial. The original workflow gave no opportunity to see any of that.&lt;/p&gt;
&lt;p&gt;I replaced the auto-commit action with &lt;code&gt;peter-evans/create-pull-request&lt;/code&gt;, which opens a PR on a branch (&lt;code&gt;automated/update-theme&lt;/code&gt;) instead of pushing to &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create Pull Request&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peter-evans/create-pull-request@v7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit-message: &amp;#39;CI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title: &amp;#39;CI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Automated theme update via `hugo mod get -u`. Please review changes before merging.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;automated/update-theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;delete-branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;add-paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; go.mod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; go.sum
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; .tugboat/config.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A few details worth calling out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;add-paths&lt;/code&gt;&lt;/strong&gt; scopes the PR to only the three files that should change. The build step generates output in &lt;code&gt;public/&lt;/code&gt;, which we don&amp;rsquo;t want to commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;delete-branch: true&lt;/code&gt;&lt;/strong&gt; cleans up the branch after the PR merges.&lt;/li&gt;
&lt;li&gt;If the theme is already up to date, there are no changes to the specified paths and no PR is opened. No noise on quiet days.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pull-requests: write&lt;/code&gt; was added to the job permissions to allow the action to open PRs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-updated-workflow"&gt;The updated workflow
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the full workflow after these changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0 0 * * *&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;update-theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull-requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peaceiris/actions-hugo@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hugo-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latest&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo mod get -u&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Tidy go.mod, go.sum&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo mod tidy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Sync Hugo version in Tugboat config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; HUGO_VERSION=$(hugo version | grep -oE &amp;#39;[0-9]+\.[0-9]+\.[0-9]+&amp;#39; | head -1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sed -i -E &amp;#34;s|/download/v[0-9.]+/hugo_extended_[0-9.]+_Linux-64bit|/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit|g&amp;#34; .tugboat/config.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build site&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo --gc --minify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create Pull Request&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peter-evans/create-pull-request@v7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commit-message: &amp;#39;CI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title: &amp;#39;CI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Update theme&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Automated theme update via `hugo mod get -u`. Please review changes before merging.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;automated/update-theme&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;delete-branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;add-paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; go.mod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; go.sum
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; .tugboat/config.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="takeaways"&gt;Takeaways
&lt;/h2&gt;&lt;p&gt;Automation that commits directly to your main branch with no validation is convenient right up until it isn&amp;rsquo;t. The combination that works better here is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated gates&lt;/strong&gt; to catch objective failures (the build doesn&amp;rsquo;t compile, the version numbers are out of sync)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human review&lt;/strong&gt; for everything that passes the automated gate but still involves external code landing in your repo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither one alone is enough. The build gate would have caught the broken template error in this case—but a future theme update that builds cleanly and introduces something undesirable would still slip through without the PR step. And the PR step alone, without the build gate, would still surface broken builds in review instead of catching them before the PR is even opened.&lt;/p&gt;
&lt;p&gt;Layers are good.&lt;/p&gt;</description></item><item><title>New Site, Who Dis</title><link>https://agentolivia.github.io/p/new-site-who-dis/</link><pubDate>Mon, 09 Feb 2026 15:18:13 -0800</pubDate><guid>https://agentolivia.github.io/p/new-site-who-dis/</guid><description>&lt;img src="https://agentolivia.github.io/p/new-site-who-dis/screenshot-new-site-who-dis-commit.png" alt="Featured image of post New Site, Who Dis" /&gt;&lt;p&gt;Used Claude.ai to help me choose a template for my Hugo site. &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
 &gt;Hugo&lt;/a&gt; is a static site generator. My prompt:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Help me pick a theme for my personal site running Hugo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actively maintained&lt;/li&gt;
&lt;li&gt;Accommodates blog posts that can have text, images, YouTube videos embedded.&lt;/li&gt;
&lt;li&gt;Responsive layout and typography
Nice to have: a gallery layout. Bonus points if it can integrate with Flickr.&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;a class="link" href="https://claude.ai/" target="_blank" rel="noopener"
 &gt;Claude&lt;/a&gt; recommended Stack, which I agreed with, based on some extensive theme evaluation I had done the previous week.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Hugo Stack—This is my top pick for you. It&amp;rsquo;s actively maintained (last updated January 2026), has 6,200+ GitHub stars, and includes PhotoSwipe integration for galleries with lightbox functionality. The card-style layout works beautifully for blog posts with mixed media, supports lazy-loading images, has dark mode, and handles YouTube embeds well. The responsive typography is clean and modern.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;The &lt;a class="link" href="https://themes.gohugo.io/themes/hugo-theme-stack/" target="_blank" rel="noopener"
 &gt;Stack&lt;/a&gt; theme, self-described as &amp;ldquo;Card-style Hugo theme designed for bloggers,&amp;rdquo; doesn&amp;rsquo;t use the typical Hugo &lt;a class="link" href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener"
 &gt;quickstart process&lt;/a&gt; for adding a theme to an existing repo using &lt;code&gt;git submodule add...&lt;/code&gt;. Since this site is brand new, I was okay with starting from scratch.&lt;/p&gt;
&lt;p&gt;The Stack theme repo lives at GitHub: &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack&lt;/a&gt;. The Quickstart process, using the template at &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack-starter&lt;/a&gt;, assumes you are starting a brand new site.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It uses &lt;a class="link" href="https://gohugo.io/hugo-modules/" target="_blank" rel="noopener"
 &gt;Hugo modules&lt;/a&gt; feature to load the theme. This means that the theme is downloaded to Hugo&amp;rsquo;s cache directory and isn&amp;rsquo;t visible in your repo. Contrast that with the &lt;code&gt;git submodule&lt;/code&gt; approach for adding a theme which adds theme code to the &lt;em&gt;themes&lt;/em&gt; directory and is managed with &lt;code&gt;git submodule&lt;/code&gt; commands. (This was a new concept for me.) Hugo modules are managed with &lt;code&gt;hugo mod&lt;/code&gt; commands, which requires Go.&lt;/li&gt;
&lt;li&gt;It comes with a basic theme structure and configuration.&lt;/li&gt;
&lt;li&gt;A GitHub action (&lt;em&gt;.github/workflows/deploy.yml&lt;/em&gt;) has been set up to deploy the theme to a public GitHub page automatically.&lt;/li&gt;
&lt;li&gt;Also, there&amp;rsquo;s a cron job to update the theme automatically everyday.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal"&gt;Goal
&lt;/h2&gt;&lt;p&gt;Create new Hugo site based on &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack-starter&lt;/a&gt; template and deploy with GitHub Pages.&lt;/p&gt;
&lt;h2 id="notes"&gt;Notes
&lt;/h2&gt;&lt;p&gt;The default branch on &lt;em&gt;CaiJimmy/hugo-theme-stack-starter&lt;/em&gt; is &lt;code&gt;master&lt;/code&gt; and I want to change that to &lt;code&gt;main&lt;/code&gt;. That will mean a few extra steps.&lt;/p&gt;
&lt;h2 id="local-development-vs-cloud-editing"&gt;Local development vs. cloud editing
&lt;/h2&gt;&lt;p&gt;I watched the video tutorial at &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack-starter&lt;/a&gt;. This demonstrates using GitHub&amp;rsquo;s Codespaces cloud editor. I decided to use a local development workflow since I am comfortable with that. Going the local development route means I&amp;rsquo;ll need the following dependencies installed on my Mac:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Git (I&amp;rsquo;ve already installed this with Command Line Tools, and set up my public key with GitHub.)&lt;/li&gt;
&lt;li&gt;Hugo (I already installed this with &lt;code&gt;brew install hugo&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Go (Required by this project, I installed it globally with &lt;code&gt;brew install go&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="steps"&gt;Steps
&lt;/h2&gt;&lt;p&gt;If you want to use GitHub Codespaces, follow the steps at &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack-starter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To set up the site for local development, I did the following steps:&lt;/p&gt;
&lt;h3 id="on-github"&gt;On GitHub
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;On &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack-starter" target="_blank" rel="noopener"
 &gt;CaiJimmy/hugo-theme-stack-starter&lt;/a&gt;, in the upper right corner, click &lt;strong&gt;Use this template&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create new repository&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Name the repo &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt; and click Create this repository (or whatever the submit button is called)&lt;/li&gt;
&lt;/ol&gt;

 &lt;blockquote&gt;
 &lt;p&gt;To make deployment to GitHub Pages a snap, I named my repo, &lt;code&gt;agentolivia.github.io&lt;/code&gt;, where &lt;code&gt;agentolivia&lt;/code&gt; is my GitHub username. If you use a different repo name, the site will be deployed to sub-directory of &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;. Fun facts.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="on-my-mac-in-a-terminal-window"&gt;On my Mac, in a Terminal window
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cd ~/Sites&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git clone git@github.com:&amp;lt;username&amp;gt;/&amp;lt;username&amp;gt;.github.io.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd &amp;lt;username&amp;gt;.github.io&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git submodule update --init --recursive&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="change-the-master-branch-to-main"&gt;Change the &lt;code&gt;master&lt;/code&gt; branch to &lt;code&gt;main&lt;/code&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git branch -m master main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push -u origin main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="back-on-github"&gt;Back on GitHub
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;On the repo page, click &lt;em&gt;Settings&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Under Settings: Default branch &amp;gt; Switch (arrows icon) &amp;gt; &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="back-on-mac-in-terminal-window"&gt;Back on Mac, in Terminal window
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git push origin --delete master&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What we have right now is a broken build which will be fixed by updating &lt;em&gt;.github/workflows/deploy.yml&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="edit-deployyml"&gt;Edit deploy.yml
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In code editor of choice, open for editing &lt;em&gt;.github/workflows/deploy.yml&lt;/em&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Side quest: Install GitHub Actions plugin on VS Code, which popped up when I opened the file in VS Code.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the list of branches to match the new default branch, &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build and deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update environment constants to match local.&lt;/p&gt;
&lt;p&gt;I noticed that when I checked out the versions I had locally installed, they were a bit different than what was listed in &lt;em&gt;deploy.yml&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# hugo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo v0.155.3+extended+withdeploy darwin/arm64 &lt;span class="nv"&gt;BuildDate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2026-02-08T16:40:42Z &lt;span class="nv"&gt;VendorInfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Homebrew
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go version go1.25.7 darwin/arm64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;v24.12.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Under &lt;code&gt;jobs.build.env&lt;/code&gt;, update the version to match the output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DART_SASS_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.97.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GO_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.25.7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.155.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;24.12.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;America/Los_Angeles&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Save file and &lt;code&gt;git add .github/workflows/deploy.yml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commit: `git commit -m &amp;ldquo;Update deploy.yml&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push to repo to see deployment in action: &lt;code&gt;git push&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="switch-to-github-to-see-deployment-in-action"&gt;Switch to GitHub to see deployment in action
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Click on the yellow dot next to the latest commit message, then Details to watch the build!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="view-live-site"&gt;View live site
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Visit GitHub pages site. For me this is at &lt;a class="link" href="https://agentolivia.github.io" target="_blank" rel="noopener"
 &gt;https://agentolivia.github.io&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It should be live with the starter template&amp;rsquo;s demo content.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;m now ready to customize the site, unpublish the default content, and add some new posts. (Hey, this is one of them, so I succeeded!)&lt;/p&gt;</description></item></channel></rss>