Fix: make --strict catch broken anchors, and fix the six it was hiding - #1581
Conversation
`validation.links.anchors` defaults to `info`, which `--strict` ignores. A link to a heading that was renamed or never existed therefore built clean and 404'd in the reader's browser. Six were live: | Link | Target | | ---- | ------ | | `ci.md#sanitizer-sim` (3 sites) | never existed; the heading is `Nightly sanitizer sweep` | | `orchestrator.md#5-ring-slot--per-scope-heap-allocator` | doubled hyphen | | `l0-swimlane-profiling.md#34-viewing--insight-vs-perfetto` | doubled hyphen | | `l0-swimlane-profiling.md#72---set-arg-floor-...` | tripled hyphen | | `args-dump.md#8-faq--debug-guide` | doubled hyphen | The last four were not typos. GitHub and MkDocs slugify differently: a heading like `## 8. FAQ / Debug Guide` drops the `/` and leaves two spaces, which GitHub renders as `8-faq--debug-guide` and MkDocs collapses to `8-faq-debug-guide`. Those links were correct in the GitHub view and broken on the published site. Repointing them would only have moved the breakage, so the four headings lose the punctuation that splits them instead — `+` to `and`, an em dash to a colon, `/` to `and`, and a leading `--set-arg` out of the heading text. Both slug dialects now agree, and every referring link is updated. With the six fixed, `anchors: warn` is enabled. Verified it bites: pointing `sanitizers.md` at `ci.md#does-not-exist` fails the build with exit 1. This also makes the comment on the build step in `.github/workflows/docs.yml` true. It has claimed since hw-native-sys#1569 that `--strict` fails on a bad anchor; it did not. Still reported at INFO and not addressed here: five links to directories with no index page (`hardware/`, `troubleshooting/`, `remote-l3-worker-design/`, `troubleshooting/device-error-codes/`). They resolve in the GitHub view and 404 on the site; fixing them means adding index pages, not editing links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 56 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`detect-changes` carried two definitions of "changing this cannot change what the code does". `NON_CODE` covered `docs/ .docs/ .claude/ .gitignore .pre-commit-config.yaml *.md`; a separate `docs_only` meant only "every file ends in .md". The arch-specific jobs gated on the wide one and the **self-hosted hardware unit tests gated on the narrow one**, so a PR touching just `.gitignore` skipped every scene test and still booked `ut-a2a3` and `ut-a5` on real silicon. `NON_CODE` also did not know about docs tooling living outside `docs/`. `mkdocs.yml` is pure docs config at the repo root, and it was the one file in PR hw-native-sys#1581 (six `docs/*.md` plus `mkdocs.yml`) that survived every filter — that PR ran the entire matrix, including `st-onboard-a2a3`, `st-onboard-a5`, `ut-a2a3` and `ut-a5`, and a pre-existing flake in the a5 L3 close path then reddened it. `docs/ci.md` documented the narrow `docs_only` as deliberate, on the grounds that "a change to `.pre-commit-config.yaml` or a `.claude/` script can still change what CI does". Neither holds. `pre-commit` is ungated and every gated job declares `needs: [detect-changes, pre-commit]`, so that config's only consumer always runs — `ut` and friends do not read it. And no workflow invokes anything under `.claude/`: `grep -rn '\.claude' .github/workflows/` finds only comments. The three scripts there are local/agent tooling. So there is now one set. `NON_CODE` gains `mkdocs.yml` and `.github/workflows/docs.yml` — the latter because `docs.yml` runs unconditionally on every PR and is therefore its own gate. `ci.yml` stays out deliberately: a change to the gates must run everything, including whatever it just switched off. `docs_only` becomes `non_code_only`, defined as "no changed file falls outside NON_CODE", and all four consumers move with it; the old name would have been a lie once the set widened past markdown. The empty-diff case is also unified. The emptiness test used to live inside the `non_code_only` branch alone, which left that flag false — UT and packaging ran — while both arch flags independently came out false too, skipping every scene test. Two axes failing in opposite directions on the same input, under a doc claiming it "runs the full matrix". A single guard now short-circuits before any pattern runs: attribution is impossible, so `non_code_only=false`, both arch flags `true`, return. The arch outputs otherwise need no change — they already subtract `NON_CODE` before deciding, so they inherit the widening. Replayed the pattern against twelve file lists. Newly skipping: docs-only, `.gitignore`-only, `.claude/`-only, `docs/_hooks/*.py`, `workflows/docs.yml`-only. Still running everything: `ci.yml`, `src/common/`, `python/simpler/`, and an empty diff. Still single-sided: `src/a5/` only, `examples/a2a3/` only, and `docs/` mixed with `src/a5/`. `docs/ci.md` moves with the code: principle 6, the job matrix, the flag list, and the two paragraphs whose rationale no longer describes the design. `.claude/rules/ci-change-detection.md` records the invariants rather than the patterns: one vocabulary, membership by effect rather than path, never gate an expensive job more loosely than a cheap one, why the two axes are layered, fail open once at the top, why a schedule trigger needs its own workflow, and that a gating bug surfaces as a green check — so it has to be verified by looking at what ran, not by reading the regex. It also names the two jobs that are deliberately ungated, `detect-changes` and `pre-commit`, rather than claiming every job is gated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…1589) `detect-changes` carried two definitions of "changing this cannot change what the code does". `NON_CODE` covered `docs/ .docs/ .claude/ .gitignore .pre-commit-config.yaml *.md`; a separate `docs_only` meant only "every file ends in .md". The arch-specific jobs gated on the wide one and the **self-hosted hardware unit tests gated on the narrow one**, so a PR touching just `.gitignore` skipped every scene test and still booked `ut-a2a3` and `ut-a5` on real silicon. `NON_CODE` also did not know about docs tooling living outside `docs/`. `mkdocs.yml` is pure docs config at the repo root, and it was the one file in PR #1581 (six `docs/*.md` plus `mkdocs.yml`) that survived every filter — that PR ran the entire matrix, including `st-onboard-a2a3`, `st-onboard-a5`, `ut-a2a3` and `ut-a5`, and a pre-existing flake in the a5 L3 close path then reddened it. `docs/ci.md` documented the narrow `docs_only` as deliberate, on the grounds that "a change to `.pre-commit-config.yaml` or a `.claude/` script can still change what CI does". Neither holds. `pre-commit` is ungated and every gated job declares `needs: [detect-changes, pre-commit]`, so that config's only consumer always runs — `ut` and friends do not read it. And no workflow invokes anything under `.claude/`: `grep -rn '\.claude' .github/workflows/` finds only comments. The three scripts there are local/agent tooling. So there is now one set. `NON_CODE` gains `mkdocs.yml` and `.github/workflows/docs.yml` — the latter because `docs.yml` runs unconditionally on every PR and is therefore its own gate. `ci.yml` stays out deliberately: a change to the gates must run everything, including whatever it just switched off. `docs_only` becomes `non_code_only`, defined as "no changed file falls outside NON_CODE", and all four consumers move with it; the old name would have been a lie once the set widened past markdown. The empty-diff case is also unified. The emptiness test used to live inside the `non_code_only` branch alone, which left that flag false — UT and packaging ran — while both arch flags independently came out false too, skipping every scene test. Two axes failing in opposite directions on the same input, under a doc claiming it "runs the full matrix". A single guard now short-circuits before any pattern runs: attribution is impossible, so `non_code_only=false`, both arch flags `true`, return. The arch outputs otherwise need no change — they already subtract `NON_CODE` before deciding, so they inherit the widening. Replayed the pattern against twelve file lists. Newly skipping: docs-only, `.gitignore`-only, `.claude/`-only, `docs/_hooks/*.py`, `workflows/docs.yml`-only. Still running everything: `ci.yml`, `src/common/`, `python/simpler/`, and an empty diff. Still single-sided: `src/a5/` only, `examples/a2a3/` only, and `docs/` mixed with `src/a5/`. `docs/ci.md` moves with the code: principle 6, the job matrix, the flag list, and the two paragraphs whose rationale no longer describes the design. `.claude/rules/ci-change-detection.md` records the invariants rather than the patterns: one vocabulary, membership by effect rather than path, never gate an expensive job more loosely than a cheap one, why the two axes are layered, fail open once at the top, why a schedule trigger needs its own workflow, and that a gating bug surfaces as a green check — so it has to be verified by looking at what ran, not by reading the regex. It also names the two jobs that are deliberately ungated, `detect-changes` and `pre-commit`, rather than claiming every job is gated.
Summary
validation.links.anchorsdefaults toinfoin MkDocs 1.6, and--strictonly fails onwarnor above. A link to a heading that was renamed or never existed therefore built clean and 404'd in the reader's browser. Six were live onmain:ci.md#sanitizer-sim(3 call sites)### Nightly sanitizer sweeporchestrator.md#5-ring-slot--per-scope-heap-allocatorl0-swimlane-profiling.md#34-viewing--insight-vs-perfettol0-swimlane-profiling.md#72---set-arg-floor-...(4 call sites)args-dump.md#8-faq--debug-guideThe last four were not typos
GitHub and MkDocs slugify differently. A heading like
## 8. FAQ / Debug Guidedrops the/and leaves two spaces; GitHub renders that as8-faq--debug-guide, MkDocs collapses it to8-faq-debug-guide. So those links were correct in the GitHub view and broken on the published site — which is why nobody noticed, and why simply repointing them would have moved the breakage rather than removed it.The fix is to take the punctuation out of the four headings so both dialects agree:
## 5. Ring (slot + per-scope heap allocator)## 5. Ring (slot and per-scope heap allocator)### 3.4 Viewing — Insight vs Perfetto### 3.4 Viewing: Insight vs Perfetto### 7.2 `--set-arg` floor for a loop count …### 7.2 Arg floor for a loop count …## 8. FAQ / Debug Guide## 8. FAQ and Debug GuideEvery referring link is updated with them.
Then the guard goes on
Verified it bites — pointing
sanitizers.mdatci.md#does-not-existgives:Correcting my own comment
.github/workflows/docs.ymlhas claimed since #1569 that--strict"fails on a dead intra-docs link, a page missing from nav, or a bad anchor". The first two were true; the third was not. This PR makes the sentence accurate rather than editing it down.Not fixed here
Five links point at directories with no index page —
hardware/,troubleshooting/,remote-l3-worker-design/,troubleshooting/device-error-codes/. They resolve in the GitHub view and 404 on the site, and MkDocs reports them at INFO underunrecognized_links. Fixing them means adding index pages, not editing links, so it is a separate change;unrecognized_linksis deliberately left at its default until then.Testing
mkdocs build --strict— exit 0, zero warningsid="8-faq-and-debug-guide"markdownlint-cli2 --config tests/lint/.markdownlint.yaml— 0 errors on all six changed docsDocs and docs-tooling only.