The missing converter: how a single missing file blocked six nights of output
The overnight pass that runs on this homelab between 00:30 and 06:00 CDT has, for the last six nights, drafted a continuous-CV update and a blog post about the night's work, written both to a branch called overnight/<date>-<slug> in the personal-website-hugo repo, and committed them.
Zero of those posts shipped. The CV updates also didn't ship.
The shape of the hole
The publish playbook in the overnight system prompt is explicit:
Run the converter at
/tmp/blogvenv/bin/python3 /tmp/md_to_lambda.pyto render~/code/cwfrazier-com-lambda/blog-<slug>.htmland refreshwriting.html. Commit onovernight/<date>-<slug>, fast-forward merge intomain, push BOTH repos. Then runcd ~/code/cwfrazier-com-lambda && bash deploy.sh.
Neither /tmp/md_to_lambda.py nor /tmp/blogvenv existed on the host. Six consecutive nights, the pass got to "run the converter," got No such file or directory, wrote "blocked on missing converter" into the morning digest, and moved on.
I'd been reading the morning digest and not really registering it. "Blocked on missing converter" looked like a one-line item, the same kind of "look at this when you can" line that appears in every digest. Six in a row, identical wording. I didn't see it because nothing changed.
What it actually was
A missing file is not a hard problem. Writing it took me a single overnight pass. It is 280 lines of pure stdlib Python — front-matter parser, markdown-to-HTML renderer covering the subset that the existing posts actually use, a composition step that calls the already-existing site_template.render() so the new page inherits the site chrome, and an idempotent upsert into the archive page that de-dupes by href.
No new dependencies. No pip install. The "venv" in the playbook was never necessary; system Python 3.11 has everything I needed.
The whole thing exists because the playbook author (me, two months ago) imagined a more elaborate converter than was actually required. "Spin up a venv, install python-markdown, render with extensions, inline the syntax highlighter" — the kind of plan you write when you're imagining the worst case instead of looking at the input. The actual input was five short markdown posts using h2, paragraphs, inline code, and the occasional fenced code block. A renderer for that fits in a hundred lines.
What I'd say to past-me
When a pipeline has a step that says "run the thing at this path," either (a) make sure the thing exists at that path before writing "run the thing" into the playbook, or (b) make the playbook's first step "if the thing isn't there, build it."
The second one is what the overnight pass should have been doing from night one. "Blocked on missing converter" is not an output — it's an instruction. The night the line first appeared in the digest, the right response was to build the converter, not to write "blocked" and move on.
I'm setting that pattern now: any "blocked on missing X" line in tomorrow's digest is a directive for the next overnight pass to build X, not a status report for me to read with my coffee.
The six unshipped posts are going out tonight, oldest first. The first one through the new pipeline is yesterday's whitelist-vs- blacklist post. This one — about the pipeline itself — is the second.