ci: format Markdown with mdformat instead of Prettier - #55
Merged
Conversation
`npx prettier@3.9.6 --check` fetched Prettier from the npm registry on every run, and that fetch intermittently sat silent for exactly 301 seconds — npm's default fetch-timeout expiring and the retry then succeeding — in a job that takes twelve seconds when it is clean. The ten-minute bound cannot catch it: a run that recovers at 301s is under the bound and always will be. Prettier also carried the last hand-edited version pin in CI, because it has no first-party action. mdformat is a Python tool, so its pin moves to pyproject.toml's dev extra where the pip Dependabot ecosystem already bumps everything else, and setup-node leaves the workflow. markdownlint-cli2 stays. Its action is JavaScript, so GitHub runs it on the runner's own Node with no toolchain to install and nothing to fetch — it already sat before setup-node, which is the proof. Replacing it too would mean giving up per-file rule config, and README.md and pull_request_template.md both depend on a markdownlint-configure-file comment. mdformat needs two extension plugins to match what Prettier did unasked. mdformat-gfm pads table cells, byte-identically, so no table in the repo moves. mdformat-frontmatter keeps a YAML block at the top of a file intact; without it mdformat does not recognise frontmatter at all and rewrites the block into a horizontal rule and a heading. It did exactly that to a skill file in contrail-gh. Nothing tracked here carries frontmatter today, so that plugin is a guard rather than a fix — but the failure is silent, and the first .md that needs it would not announce itself. `number = true` is load-bearing too: without it every ordered list is renumbered to `1.`. .mdformat.toml succeeds .prettierignore as the one ignore list. Its `exclude` key errors below Python 3.13, so both call sites pin 3.13 and the dev venv moves with them; `requires-python` is untouched and the matrix still tests 3.11. Writing those entries twice instead, as a shell filter and a pre-commit regex, would buy only the ability to format Markdown on an interpreter nobody here runs. It also has to name `venv/`, which .prettierignore did not: Prettier and markdownlint-cli2 read .gitignore, and mdformat does not. One line in AGENTS.md hard-wrapped an inline code span across a line break. mdformat cannot break inside a span, so it joined the paragraph into 150 characters and MD013 failed. Shortened the span and rewrapped. The mechanical reformat is the next commit, so this one stays reviewable. Refs atdr/contrail-gh#8 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sxtf4vfR3TyVrQXdixJXq
Mechanical, kept out of the previous commit so that one stays reviewable. No prose changed. Every table is byte-identical: mdformat-gfm pads cells exactly as Prettier did. What moved is smaller and in four shapes. Blank lines between the items of an already-loose list, in AGENTS.md and docs/resync.md. CommonMark makes a list loose if any item is, and mdformat renders that consistently rather than leaving some items separated and some not. Renders the same either way. A blank line between two adjacent HTML comments, in README.md and pull_request_template.md. Both files keep their markdownlint-configure-file comment working; markdownlint reports 0 issues in 13 files. Two-space continuation indent on a task-list item in pull_request_template.md, where Prettier aligned under the text after `- [ ] `. Reference link labels lowercased and sorted in CODE_OF_CONDUCT.md, which is vendored Contributor Covenant text. mdformat normalises them and offers no way to opt out. Link targets are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mJ5NaA5DdPxqa1Mb4UJhT
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Prettier is gone. mdformat formats Markdown instead, markdownlint-cli2 stays exactly as it was, and
setup-nodeleavesci.ymlentirely.Why
npx prettier@3.9.6 --checkrefetched Prettier from the npm registry on every run, and that fetch intermittently sat silent for exactly 301 seconds — npm's defaultfetch-timeoutexpiring, the retry then succeeding — in a job that takes twelve seconds when it is clean. atdr/contrail-gh#8 measured three stalls in six runs. The ten-minutetimeout-minutesbound cannot catch it and never could: a run that recovers at 301s is under the bound by design.Prettier also carried the last hand-edited version pin in CI, because it has no first-party action. mdformat is a Python tool, so the pin moves into
pyproject.toml's dev extra where the pip Dependabot ecosystem already bumps everything else.Refs atdr/contrail-gh#8. The matching change in the template is atdr/contrail-gh#16, and the two should land close together —
docs/contrail-gh.mdsays these config files are copies, so a gap means the two repos disagree about what correct Markdown is. Land this one first; that PR's message says it matches this.Why 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 from a registry — it already sat before
setup-node, which is the proof. Replacing it too would meanpymarkdownlnt, which has no per-file rule configuration, and two files here depend on amarkdownlint-configure-filecomment:README.md(MD013 at 100) and.github/pull_request_template.md(MD041 off, because it opens with a comment by design)."No Node in CI" was never achievable anyway —
actions/checkoutandactions/setup-pythonare themselves JavaScript actions. "No Node toolchain to install, pin and fetch" is, and that is what this does.Changes
Tooling (
4aec3ff)ci.ymldocsjob:setup-node+npx prettier@3.9.6out;setup-pythonat 3.13,pip install -e ".[dev]"andmdformat --check .in.prettierrc.jsonand.prettierignoredeleted;.mdformat.tomladded as their successor.pre-commit-config.yaml:rbubley/mirrors-prettier→hukkin/mdformat(the official Prettier mirror is archived, which is why we were on a fork)pyproject.toml:mdformat,mdformat-gfmandmdformat-frontmatterin the dev extraAGENTS.md,.markdownlint-cli2.yaml,docs/contrail-gh.mdupdated to matchMechanical pass (
8f800fa) — 5 files, 17 insertions, 6 deletions, no prose changed. Kept separate so the first commit stays reviewable, same shape as #23.Two things worth a reviewer's attention
Python 3.13 for the formatter only.
.mdformat.toml'sexcludekey hard-errors below 3.13, soci.yml'sdocsjob and the pre-commit hook both pin it, and the dev venv moves with them.requires-pythonis untouched and the matrix still tests 3.11, 3.12 and 3.13 — the interpreter that formats Markdown has nothing to do with the one a user installscontrailson. The alternative was writing the same three exclusions twice in two syntaxes, a shell filter and a pre-commit regex, which is the hazard.pre-commit-config.yamlalready warns about, bought for nothing but the ability to format on an interpreter nobody here runs.mdformat-frontmatteris a guard, not a fix. Without it mdformat does not recognise YAML frontmatter at all and rewrites the block into a horizontal rule and a heading. It did exactly that to a committed skill file in contrail-gh, caught by reading the diff. Nothing tracked here carries frontmatter today, but the failure is silent and the first.mdthat needs it would not announce itself..mdformat.tomlalso has to namevenv/, which.prettierignoredid not: Prettier and markdownlint-cli2 both read.gitignore, and mdformat does not. Without it a full-tree run walks intosite-packagesand finds hundreds of other projects' READMEs.Checks
./venv/bin/ruff check .and./venv/bin/ruff format .pass./venv/bin/pytest -qpassesgh issue listfor open issues this change touches — none of the six are affectedTest plan
Run locally on Python 3.13, before and after the mechanical pass:
mdformat --check .exits 0, and re-running changes nothing, proving idempotencepre-commit run --all-filespasses every hook, with the mdformat hook env reporting Python 3.13.15pytest -q— 319 passedmdformat-gfmpads cells byte-identically to PrettierREADME.mdare untouched by the formatting passCLAUDE.mdis still a symlink afterwards, not replaced by a regular filemarkdownjob ran in 18s, and all threepytestlegs (3.11, 3.12, 3.13) still pass, confirming the dev-tooling Python bump did not touch the supported range🤖 Generated with Claude Code
https://claude.ai/code/session_011mJ5NaA5DdPxqa1Mb4UJhT