Automations Anonymous
Sheet
Display
Read
Type

Sheet 04 · /automations/fail-the-build-when-published-content-stops-being-valid

Fail the build when published content stops being valid

A CI step parses every code snippet a site publishes using the real tool for its declared format, so a snippet that stops being valid breaks the build instead of sitting published and wrong.

Difficulty:
beginner

Problem

Documentation and directories publish code that is never executed again after it is written. It rots on someone else's release schedule, and the first person to find out is a reader who pastes it and it fails.

Trigger

Every push, through the CI workflow, and weekly against production.

Prerequisites

  • 01Snippets stored as data with a declared format, not embedded in prose.
  • 02The checkers available on the runner. bash, node and python3 ship on GitHub-hosted Ubuntu.

Steps

  1. 01

    Store the declared format alongside every snippet

    Without it there is nothing to check against, and a checker has to guess.

  2. 02

    Parse each snippet with the real tool for its format node

    bash -n for shell, node --check for JavaScript, a YAML and XML parse, a field count for cron. Write to a temp file whose extension the tool expects.

  3. 03

    Check the content rules you claim to hold, in the same pass

    Slug shape, field lengths, ordering, references that must resolve. A rule nothing enforces is a preference.

  4. 04

    Exit non-zero and name every failure github-actions

    One report listing every problem beats failing on the first one.

Payload · yaml

name: CI

on:
  push:
    branches: ["**"]
  pull_request:

# The operator's machine cannot build this project: Turbopack fails to bind a
# worker port there, so local builds run --webpack while Vercel builds with
# Turbopack. That makes CI the only place a trustworthy build signal exists.
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  verify:
    runs-on: ubuntu-latest
    env:
      # Public by construction: this is the NEXT_PUBLIC_ Convex URL the browser
      # already receives. Static generation reads published records at build.
      NEXT_PUBLIC_CONVEX_URL: https://exciting-deer-586.convex.cloud
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci

      - name: Types
        run: npx tsc --noEmit

      - name: Lint
        run: npm run lint

      - name: Corpus conformance
        run: node scripts/check-payloads.mjs

      - name: Build
        run: npx next build

      - name: Audit production dependencies
        run: npm audit --omit=dev --audit-level=high

Failure modes

  • !Node picks a module format from the file extension, so a snippet written to a .javascript file fails to parse for a reason that has nothing to do with the snippet.
  • !A parse check proves syntax and nothing else. A script that parses cleanly and deletes the wrong directory passes.
  • !Checking only the source file misses drift in what is actually published, if the two can differ.

JSON-LD · HowTo

{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Fail the build when published content stops being valid",
  "description": "A CI step parses every code snippet a site publishes using the real tool for its declared format, so a snippet that stops being valid breaks the build instead of sitting published and wrong.",
  "url": "https://automationsanonymous.com/automations/fail-the-build-when-published-content-stops-being-valid",
  "tool": [
    {
      "@type": "HowToTool",
      "name": "github-actions",
      "url": "https://automationsanonymous.com/tools/github-actions"
    },
    {
      "@type": "HowToTool",
      "name": "node",
      "url": "https://automationsanonymous.com/tools/node"
    }
  ],
  "step": [
    {
      "@type": "HowToStep",
      "position": 1,
      "name": "Store the declared format alongside every snippet",
      "text": "Without it there is nothing to check against, and a checker has to guess."
    },
    {
      "@type": "HowToStep",
      "position": 2,
      "name": "Parse each snippet with the real tool for its format",
      "text": "bash -n for shell, node --check for JavaScript, a YAML and XML parse, a field count for cron. Write to a temp file whose extension the tool expects.",
      "url": "https://automationsanonymous.com/tools/node"
    },
    {
      "@type": "HowToStep",
      "position": 3,
      "name": "Check the content rules you claim to hold, in the same pass",
      "text": "Slug shape, field lengths, ordering, references that must resolve. A rule nothing enforces is a preference."
    },
    {
      "@type": "HowToStep",
      "position": 4,
      "name": "Exit non-zero and name every failure",
      "text": "One report listing every problem beats failing on the first one.",
      "url": "https://automationsanonymous.com/tools/github-actions"
    }
  ],
  "datePublished": "2026-09-06T19:19:25.857Z"
}