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
2
# cover uniqueness: any output means duplicates
grep -h '^cover:' source/_posts/*.md | sort | uniq -d

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
2
3
4
referenced = extract /images/... paths from all bodies
on_disk = list the image directory
missing = referenced - on_disk # will 404 live
stale = on_disk - referenced # dead weight to clean up

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.

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