Skip to content

ci: format Markdown with mdformat instead of Prettier - #16

Merged
atdr merged 2 commits into
mainfrom
ci/replace-prettier-with-mdformat
Sep 9, 2026
Merged

ci: format Markdown with mdformat instead of Prettier#16
atdr merged 2 commits into
mainfrom
ci/replace-prettier-with-mdformat

Conversation

@atdr

@atdr atdr commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Closes The Prettier fetch stalls for five minutes on most runs #8. Prettier is gone; mdformat formats Markdown instead, and setup-node leaves markdown.yml entirely.
  • The last hand-edited version pin goes with it. mdformat's pins live in a new requirements-dev.txt, which the pip Dependabot entry already configured in .github/dependabot.yml picks up.
  • markdownlint-cli2 stays exactly as it was.

Matches atdr/contrail#55, which should land first. docs/contrail-gh.md upstream says these config files are copies across this repo, contrail and every instance, so a gap means the repos disagree about what correct Markdown is.

Why

#8 measured it: npx prettier@3.9.6 --check refetched Prettier from the npm registry on every run, and the fetch sat silent for exactly 301 seconds — npm's default fetch-timeout expiring, the retry then succeeding — in a job that takes twelve seconds clean. Three stalls in six runs. #7's ten-minute bound cannot catch it and never could: a run that recovers at 301s is under the bound by design.

Prettier's pin could not be handled by Dependabot, because it has no first-party action, so it sat in a run: line as a documented wart. That is now gone too.

markdownlint-cli2 stays because its action is JavaScript: GitHub runs it on the runner's own Node with no toolchain to install and nothing fetched. It already sat before setup-node, which is the proof. It also honours the per-file markdownlint-configure-file comment that keeps README.md at 100 columns, which no Python linter offers.

Changes

Tooling (c4eb64a)

  • markdown.yml: setup-node + npx prettier@3.9.6 out; setup-python at 3.13 and pip install -r requirements-dev.txt, then mdformat --check .
  • requirements-dev.txt added, holding mdformat, mdformat-gfm and mdformat-frontmatter
  • .prettierrc.json and .prettierignore deleted; .mdformat.toml added as their successor
  • check-template.yml: the guard that markdown.yml's config ships beside it now names .mdformat.toml and requirements-dev.txt
  • README.md and AGENTS.md updated, including all three copies of the template-update recipe that list the config files by name

Mechanical pass (43aaaca) — one line, a blank line between two adjacent HTML comments at the foot of README.md.

Why requirements-dev.txt rather than requirements.txt

requirements.txt carries the contrail pin a sync installs, and sync.yml, check-instance.yml and check-template.yml all read it. A Markdown formatter has no business in there, and an instance pulling Markdown config should never have its own version pin touched as a side effect. Verified that Dependabot's pip ecosystem does bump requirements-dev.txt alongside requirements.txt before relying on it.

It is in the check-template.yml guard because a missing pin file fails the job outright, rather than quietly checking the wrong thing the way a missing .markdownlint-cli2.yaml would.

One thing worth a reviewer's attention

mdformat silently destroyed the YAML frontmatter in .claude/skills/two-audience-docs/SKILL.md. It rewrote the block into a horizontal rule and a ## name: two-audience-docs description: … heading, which would have broken the skill with nothing to complain about. Prettier understood frontmatter natively; mdformat does not recognise it at all without mdformat-frontmatter. Caught by reading the mechanical diff before committing it. The plugin is pinned and requirements-dev.txt says why.

.mdformat.toml's exclude key errors below Python 3.13, which is why markdown.yml pins that version. A runner provides any version on request, so this costs an instance nothing.

Test Plan

  • mdformat --check . exits 0, and re-running changes nothing, proving idempotence
  • markdownlint-cli2 v0.23.2 reports 0 issues in 4 files against mdformat's output — the two tools agree rather than fight
  • The frontmatter in .claude/skills/two-audience-docs/SKILL.md is byte-identical after a format pass, with mdformat-frontmatter installed
  • No table moved. mdformat-gfm pads cells byte-identically to Prettier
  • README.md's markdownlint-configure-file comment still holds it at 100 columns after the blank line was inserted above it
  • All three files the check-template.yml guard now names are present
  • check-template / template contract green on this PR — passed in 7s, so the edited guard finds .mdformat.toml and requirements-dev.txt where it now expects them
  • markdown / lint green on this PR — 7s and 10s across two runs, against the baseline The Prettier fetch stalls for five minutes on most runs #8 measured of 12s when the npm fetch was clean and 301s when it was not. That is the whole point of the change, and it is now bounded by a pip install of 64 KB rather than an npm fetch of 8 MB

🤖 Generated with Claude Code

https://claude.ai/code/session_011mJ5NaA5DdPxqa1Mb4UJhT

atdr and others added 2 commits September 10, 2026 00:04
Closes #8.

`npx prettier@3.9.6 --check` refetched Prettier from the npm registry on every
run, and that fetch sat silent for exactly 301 seconds — npm's default
fetch-timeout expiring, the retry then succeeding — in a job that takes twelve
seconds when it is clean. #8 measured three stalls in six runs. The ten-minute
bound in #7 cannot catch it, and never could: a run that recovers at 301s is
under the bound by design.

mdformat is a Python tool, so setup-node leaves the workflow and the pin moves
into requirements-dev.txt, which the pip Dependabot entry already configured in
.github/dependabot.yml picks up. Prettier's pin could not be handled that way,
which is why it sat in a `run:` line as a known hand edit.

requirements-dev.txt is separate from requirements.txt on purpose. That file
carries the contrail pin a sync installs, and sync.yml, check-instance.yml and
check-template.yml all read it; a Markdown formatter has no business in there,
and an instance pulling Markdown config should never touch its own pin.

markdownlint-cli2 stays. Its action is JavaScript, so GitHub runs it on the
runner's own Node with no toolchain to install and nothing fetched — it already
sat before setup-node, which is the proof. It also honours the per-file
markdownlint-configure-file comment that keeps README.md at 100 columns, which
no Python linter offers.

mdformat needs two extension plugins to match what Prettier did without being
asked. mdformat-gfm pads table cells, byte-identically, so no table moves.
mdformat-frontmatter keeps the YAML block at the top of a Markdown file intact;
without it mdformat does not recognise frontmatter at all and rewrote
.claude/skills/two-audience-docs/SKILL.md into a horizontal rule and a heading,
which would have broken the skill silently.
`number = true` is load-bearing: without it every ordered list is renumbered to
`1.`. .mdformat.toml's `exclude` errors below Python 3.13, so markdown.yml pins
that version; a runner provides any version on request, so this costs an
instance nothing.

check-template.yml guarded that markdown.yml's config ships beside it. That
guard now names .mdformat.toml and requirements-dev.txt, the latter because a
missing pin file fails the job outright rather than quietly checking the wrong
thing.

Matches atdr/contrail, where the same swap landed first. The config files are
copies across the two repos and every instance, so they have to move together.

The mechanical reformat is the next commit, so this one stays reviewable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mJ5NaA5DdPxqa1Mb4UJhT
Mechanical, kept out of the previous commit so that one stays reviewable. No
prose changed.

One line: a blank line between two adjacent HTML comments at the foot of
README.md. The markdownlint-configure-file comment that holds this file at 100
columns still works — markdownlint reports 0 issues in 4 files.

Every table is byte-identical, and the YAML frontmatter in
.claude/skills/two-audience-docs/SKILL.md is untouched, both because of the
plugins pinned in requirements-dev.txt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mJ5NaA5DdPxqa1Mb4UJhT
@atdr
atdr merged commit 226ffb1 into main Sep 9, 2026
9 checks passed
@atdr
atdr deleted the ci/replace-prettier-with-mdformat branch September 9, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The Prettier fetch stalls for five minutes on most runs

1 participant