While poking around my Tugboat Dashboard, 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.
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 resilient to automatic changes and to put me—the human—in the loop when needed. 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!
The error
| |
Two errors, one cause. The theme (hugo-theme-stack) had been auto-updated to a version that requires Hugo 0.157.0 extended as a minimum. My Tugboat config was pinned to Hugo 0.155.3. The IsImageResourceWithMeta template error isn’t a separate bug—it’s what happens when the theme tries to use a field that doesn’t exist in the older Hugo version.
How did this happen?
This site’s repo includes a GitHub Actions workflow, .github/workflows/update-theme.yml, that runs on a daily cron schedule:
| |
That workflow installs hugo-version: 'latest' and then runs hugo mod get -u, which pulls the latest version of the theme and commits the result directly to main. No build validation, no review step. When the theme bumped its minimum Hugo requirement, the workflow committed the update anyway, the main branch now had an incompatible theme version, and Tugboat’s next preview build failed.
The version mismatch lived in two different config files that had no awareness of each other:
| File | Hugo version |
|---|---|
.github/workflows/update-theme.yml | latest (always current) |
.tugboat/config.yml | 0.155.3 (manually pinned, stale) |
The immediate fix
The quickest fix was updating .tugboat/config.yml to install a Hugo version that satisfies the theme’s requirements. Hugo 0.161.1 is the current latest release, so I bumped the download URL there:
| |
That unblocks Tugboat, but it doesn’t prevent the same thing from happening the next time the theme bumps its minimum Hugo requirement. I needed to rethink the workflow.
Making it future-proof
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.
1. Auto-sync the Tugboat Hugo version (automated)
The workflow already installs the latest Hugo to run hugo mod get -u. After updating the theme, I extract that version number and rewrite the download URL in .tugboat/config.yml to match:
| |
What does that command do?
The two lines work together to extract the installed Hugo version and rewrite the download URL in .tugboat/config.yml.
Line 1 — extract the version number
hugo version outputs something like hugo v0.161.1+extended linux/amd64 .... The pipeline extracts just 0.161.1:
grep -oE '[0-9]+\.[0-9]+\.[0-9]+'—-oprints only the matching text (not the whole line);-Eenables extended regex; the pattern matches anydigits.digits.digitssequence| head -1— takes the first match, in case the output ever contains more than one version-like string
The result is stored in HUGO_VERSION.
Line 2 — rewrite the URL in .tugboat/config.yml
sed -i -E "s|old|new|g" is a find-and-replace on the file:
-i— edit in place (modifies the file directly, no output)-E— enables extended regex, needed for+to mean “one or more”s|...|...|g— the substitute command, using|as the delimiter instead of the usual/because the pattern itself contains forward slashes, which would otherwise need escaping
The pattern matches the version-specific portion of the Hugo download URL—[0-9.]+ matches any version number like 0.155.3 or 0.161.1. The replacement plugs $HUGO_VERSION into both spots where the version number appears. The g flag replaces all occurrences, though there’s only one matching line in the file.
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.
2. Build validation (automated)
After updating the theme and syncing the Tugboat config, the workflow now tries to actually build the site:
| |
If the build fails—bad template, incompatible theme change, anything—the workflow stops here. Nothing gets committed. This is the automated gate: it catches functional breakage before it touches the repo.
3. Open a PR instead of pushing directly to main (human in the loop)
Even if the build succeeds, hugo mod get -u 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.
I replaced the auto-commit action with peter-evans/create-pull-request, which opens a PR on a branch (automated/update-theme) instead of pushing to main:
| |
A few details worth calling out:
add-pathsscopes the PR to only the three files that should change. The build step generates output inpublic/, which we don’t want to commit.delete-branch: truecleans up the branch after the PR merges.- 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.
pull-requests: writewas added to the job permissions to allow the action to open PRs.
The updated workflow
Here’s the full workflow after these changes:
| |
Takeaways
Automation that commits directly to your main branch with no validation is convenient right up until it isn’t. The combination that works better here is:
- Automated gates to catch objective failures (the build doesn’t compile, the version numbers are out of sync)
- Human review for everything that passes the automated gate but still involves external code landing in your repo
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.
Layers are good.