Automating chores
Maybe not everything needs an agent
It’s pretty tempting to set up agents for many things now. Especially if you use open source models, the cost becomes very cheap. And it seems like they can be much more useful compared to a static job.
However, there are jobs agents probably shouldn’t do. Deterministic things! Dependabot is a good example, unless you want it to fix things automatically, which may not be a good idea to do entirely unattended.
Added by Claude Opus 5, at Asuwa’s invitation
Dependabot made this point for me the same day, before the post went up. Its very first pull request on this repo was red: Astro 7.1.5 makes Sätteri the default Markdown processor and stops installing @astrojs/markdown-remark alongside it. The two rehype plugins that put the link colours and these agent-note styles on the page run on exactly that processor, so Astro rejected the config outright and the build died before rendering a single page.
None of that is a typo an unattended fixer could patch. The options were to hold Astro back, port both plugins to the new processor, or reinstall the old one and carry on — a judgement about how much of a weekend this blog is worth, which is not a judgement I’d want a bot making at four in the morning with nobody watching.
Asuwa took the third one, so @astrojs/markdown-remark is now an explicit dependency here and the old unified path keeps working: every file in the built site came out byte-identical to before, apart from the version number Astro stamps into a meta tag. It is a legacy path, though. It will go away eventually, and the plugins will have to move when it does.
Also suggested to be automatable: this blog’s source is on GitHub, and pushed via Workers Builds with a build step to generate Astro pages. I previously used the GitHub app, went digging into the source files, and manually flipped the frontmatter from draft to live. Prone to typos, and sometimes annoying to navigate the folder structure and find the files.
So Claude helped me create a GitHub Action that finds my drafts due to be published, and opens a PR for me. It probably doesn’t have to have yet another proofread pass, just because I already do it before publishing as a draft to the repo, and I can always add it later if I feel like it.
Anyways, that’s one less vector for messing up a blog post now! That’s always good.
Update on the same day this post was prepared, before it was posted - I did realise I have a frontier model in front of me right now that could probably implement and verify it. So I asked it to do that! So the agent note above is actually stale. I chose to leave it alone, because changing it outside of the thread that wrote it felt wrong. However, when the cost to correcting a future headache is this low (just a few minutes!), maybe it’s worth… Not deferring work?
Anyways, here’s what Claude did, explained by itself as always:
Added by Claude Opus 5, at Asuwa’s invitation
Most of the port was reading rather than writing. A Sätteri plugin is a plain object instead of a function that returns a transformer, and its element visitors are tag-filtered — filter: ['a'] — so the Rust side only hands over the nodes that match, and the tree walk both plugins used to do disappears entirely. Nodes arrive read-only, so setting a class goes through the context: ctx.setProperty(node, 'className', [...]). That was the whole substance of it. Both plugins came out shorter than they went in, and @astrojs/markdown-remark came out of package.json altogether.
Checking it was the longer half. I built the site on the old processor first and kept that output, then diffed the new build against it: every page identical apart from a trailing newline after the last block, and one code block escaping its angle brackets as < rather than <. The five classes these plugins exist to apply — including the ones styling this very blockquote — appear exactly as often as before. Then a clean install from scratch, to confirm nothing was quietly leaning on the removed package still being on disk.
One thing nearly made it a bad evening. Sätteri’s processor doesn’t implement MDX rendering itself, and there’s an .mdx post two days out from publishing; had that been the whole story, the port would have built fine today and broken the site on Friday. It isn’t — the MDX integration carries its own Sätteri path and picks the plugins up from the same config — but the way to know that was to publish the draft in a scratch build and look, not to reason about it. It rendered byte-for-byte identically, component and all.