The Blog Production Line (Part 3): Six Automated Pre-Publish Checks
My blog runs as a batch production line: articles are generated by agents, admitted by a validator, and built and deployed fully automatically. A fast production line speeds up its own pitfalls too — while auditing a batch of new articles the other day, ten-odd minutes surfaced six classes of problems. Each one, taken alone, is not fatal; together they are enough to make visitors frown: gray blocks on the home page, 404 links, images that don’t match the text.
This post organizes the checklist into six checks, all doable with a few lines of script, each with a real case I actually hit.
Check 1: The Cover Exists and Is Unique
My theme (Butterfly) reads the home-page thumbnail only from the front matter’s cover field and shows a gray block when it’s missing. The subtler issue is uniqueness: two articles using the same Unsplash photo look jarring when they appear side by side on the home page. This red line used to rely on the human eye; with batch output, the machine has to check.
1 | # cover uniqueness: any output means duplicates |
Real case: a newly published article and one from three months earlier used the same image — image selection is semantic at generation time, and two similarly-themed articles collided. The fix was swapping the image on the later post, and “every cover must be checked for site-wide duplicates” went straight into the validator.
Check 2: Markdown Structure — a Heading Must Own Its Line
A form of corruption unique to generated content: a heading glued to the end of the previous line, for example a sentence that ends and is immediately followed by ## plus a heading, with no line break between. The eye barely notices while reading; at render time the entire section collapses into plain text and the table of contents silently loses an entry.
The scan logic is one regex: find lines where “non-hash characters are immediately followed by a hash heading” (skipping the inside of code blocks). The last audit scanned 98 articles and found 2 occurrences in 1 article, both from the same batch’s line-concatenation bug — structural problems are never isolated incidents; after the first hit, check the whole batch.
The root cause is worth recording too: in batch generation, agents often implement “add a small section” as appending text to the end of some line, then forget the line break. The upstream fix is a generation rule mandating “appended content must start with a line break” — but when the source can’t be controlled, the scan is the last net. The two measures complement each other; it is not either-or.
Check 3: Image-Text Correspondence
Images get two layers of validation: every image path referenced in the body exists on disk; and, conversely, every image on disk is referenced by the body — the latter determines whether the deployment carries stale images that should have been cleaned. The implementation is a set difference:
1 | referenced = extract /images/... paths from all bodies |
During the renovation of 93 articles, this check cleared 75 stale images. Note that both directions must be checked: a team that only checks “missing” will quietly accumulate hundreds of unreferenced historical images in the repo.
Check 4: External Links Are Reachable
In-article links are the easiest failure point to overlook, because at writing time they “look right.” Two real cases: a footer link pointing to a GitHub repo that looked perfectly normal — but the repo was never created, and visitors got a 404; elsewhere, an npm package link whose curl returned 403 and was nearly deleted as dead — a check against the registry API showed the package alive and well.
So external-link checking has two rules of thumb: run every link through its HTTP status code one by one; and for anti-scraping sites like npm, don’t read the page status code — read the registry API. There is also one discipline: never link a repo that doesn’t exist yet; if you mean “will be open-sourced soon,” say it in words.
Check 5: Body-Text Character Integrity
Generated content occasionally mixes in replacement characters (U+FFFD) and garbled segments. I once caught “knows the earth below” rendered as four and a half broken glyphs with mojibake — nearly impossible to spot by proofreading, located in one second with grep -c $'\uFFFD'. I swept the whole site while at it and confirmed that was the only occurrence.
Check 6: Dates Must Include Time — the Most Hidden Check
This one was caught only recently and deserves its own walkthrough.
Hexo’s permalink is :year/:month/:day/:title/, and the date comes from front matter. If my new article writes only date: 2026-09-12 (no time), Hexo parses it as midnight. The question is midnight in which time zone: my machine is UTC+8, so a local build produces /2026/09/12/; GitHub Actions’ build machine is UTC, so the same date parses to September 11 and the live URL becomes /2026/09/11/.
Both sides are “correct,” and together they are a disaster: local verification looks perfect, the live path is off by a day, and every internal link, verification script, and outbound link written with the local path breaks. At the time I curled the live site with the local path, got a 404, and nearly misjudged it as a deployment failure.
The fix is minimal: always write dates in full, 2026-09-12 10:00:00. With the time landing in daylight hours, both time zones parse the same date. While at it, sweep the whole site and backfill timestamps on every date-only entry — note this changes live URLs, so whether to touch historical articles requires weighing existing outbound links.
Closing: The Checks Join the Production Line
All six checks together are under a hundred lines of script and run in about ten seconds. They now hang at fixed positions in the publishing flow: generation → validator → six checks → build → deploy → spot-check live.
There is a clean division of labor: the per-article validator governs “does this article meet the spec” — front-matter fields, length standards, syntax discipline; the six checks govern “does this batch fit the site” — cover dedup, image set difference, site-wide structure scans, all comparing one new article against the entire corpus. Both layers are indispensable; with only the first, problems keep sneaking in “between articles.”
On timing: the structure scan and cover dedup are cheap enough to hang on the local pre-push hook; the external-link check involves real network requests and needs only one run in the publishing flow. The earlier a check runs, the cheaper the fix — that old principle holds just as well in content engineering.
The lesson of pipeline-style writing can be summed up in one sentence today: the ceiling of generation capability caps output volume; the floor of automated checks caps reputation. Everyone is competing on the former; the latter depends entirely on these unglamorous scripts.
Postscript: The Night Check 6 Was Born
This very article’s own publishing process reenacted Check 6 exactly.
The first draft set both articles’ dates to 5 a.m. the same day (UTC+8). Deployment succeeded, the health check returned 200, everything looked fine — until visits via the regular paths all 404’d. The UTC build machine parsed 5 a.m. UTC+8 into the previous day’s date, the live URLs all drifted by a day, and the local build showed nothing amiss.
Handling followed the checklist: move the dates to times that don’t cross the date boundary; sweep the site for date-only entries and backfill timestamps; and backfill two new checks into the validator — date must not be later than the push moment, and times before 08:00 UTC+8 will drift across the date boundary.
That is why Check 6 dares to call itself “the most hidden”: rules are never prophetic wisdom; they are the sediment of incidents — including the one this article itself stepped in.
In this series: (1) I Built an AI Production Line, Then the First Article Taught Me a Lesson (Chinese) | (2) 10 Parallel Agents Renovated 93 Old Articles in One Day (Chinese) | (3) this post · Pre-Publish Checks