Skip to content

ci: check docs.json redirects that cannot work - #2369

Merged
GigaHierz merged 10 commits into
mainfrom
palango/redirect-check
Oct 7, 2026
Merged

GigaHierz merged 10 commits into
mainfrom
palango/redirect-check

Conversation

@palango

@palango palango commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Mintlify accepts docs.json redirects that can never fire, and CI doesn't notice them. Between :slug* wildcards the first match wins, so a narrower wildcard listed below a broader one is dead; #2367 shipped one, which #2368 has since fixed. A source with # never matches, because browsers don't send the fragment. This adds scripts/check_redirects.py, which applies Mintlify's matching rules to docs.json, and CI runs it and its tests next to the existing orphan check. Main fails it on 27 problems today, 26 # sources and the global "Docs for AI" anchor, which still links to /build-on-celo/build-with-ai/use-docs-with-ai, a path a wildcard now redirects. The PR fixes all 27: it deletes the fragment sources and points the anchor at /build/agents/use-docs-with-ai.

What the check fails on

  • a source containing #
  • a :slug* wildcard that an earlier wildcard, broader or identical, always matches first
  • a source that captures a page in navigation
  • a navigation link to a redirected path (the message names the destination to link instead)
  • an exact destination that is /, is redirected again, doesn't exist, or isn't in navigation
  • a /b/:slug* destination where /b is not a folder, a page or a redirect source

Precedence follows what we checked against mint dev and production: an exact source beats any wildcard regardless of order, and a wildcard also matches its bare prefix.

The only wildcard the check understands is a trailing /:slug*, which is all docs.json uses. If a source has any other path parameter (/a/:path*, /a/:id, /a/:slug*/b), or redirects or navigation is missing or empty, it stops with an error rather than guess.

The check doesn't flag chains through wildcard destinations. Renaming a section depends on them: the wildcard from /build-on-celo/:slug* to /build/:slug* passes old paths on to the newer exact entries. Because of that it also misses a loop made of two wildcard destinations. It doesn't flag a redirect whose source is a page on disk that navigation doesn't list either, although Mintlify serves the page and the redirect never fires. No such source exists today. Both are tracked in #2380. Nothing records which URLs a wildcard is meant to cover, so the check also can't tell when a removed catch-all strands old URLs.

Deleted fragment redirects

I wanted to be sure Mintlify's client-side code didn't match these either, so I loaded two in headless Chrome. /celo-gold-holder-guide/ledger#install-the-celo-cli landed on /tooling/wallets/ledger/setup, which is where the bare path redirected before #2368, not on this entry's ledger/to-celo-cli. /celo-owner-guide/quick-start#vote-for-a-validator-group landed on self-custody, not staking/voting. Deleting the entries changes nothing a visitor sees.

The branch is rebased on main after #2368. I resolved docs.json by taking main's file and removing only the entries whose source contains #, then checked that every other redirect is still there, in the same order.

Verification

$ python3 -m unittest discover -s scripts -p 'test_*.py'
Ran 25 tests ... OK
$ python3 scripts/check_redirects.py
No redirect problems found.
$ bash scripts/check-orphans.sh
No orphan pages found.
$ npx mintlify@4 validate
success build validation passed
$ npx mintlify@4 broken-links --check-redirects
success no broken links found

My local npm only allows Mintlify versions published before 28 July, so these ran on 4.2.746 rather than the pinned 4.2.920. CI runs 4.2.920.

@GigaHierz GigaHierz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head 5a15fc6 · API head == refs/pull/2369 (asserted); 4 files of substance (script, tests, workflow, docs.json) plus AGENTS.md and scripts/README.md; 1 commit behind main (a12466b / #2367); git merge-tree --write-tree origin/main CONFLICTS in docs.json; the script on origin/main reports 27 problems, on #2368's head (59ef059) 27 (26 # sources plus 1 nav-link hit), on the PR head 0; 14/14 unit tests pass; on a correct merge into main the check exits 1 with 1 problem; gh pr checks: broken-links pass, GitGuardian pass, StepSecurity pass, Mintlify Deployment skipping; broken-links and orphan check clean on that merge

Verdict: REQUEST-CHANGES

Findings

  1. HIGH · docs.json (the redirects block, /home/protocol/transaction/:slug* at main:633 after /home/:slug* at main:613) · The PR is green only because it is one commit behind main. #2367 landed since, adding a wildcard that the new check correctly reports as dead, so the first correct merge goes red, and since the workflow also triggers on push to main, main itself goes red after the merge. Failure scenario: I merged origin/main into the PR head and resolved docs.json properly (see finding 2); python3 scripts/check_redirects.py then prints /home/protocol/transaction/:slug*: never matches, because /home/:slug* comes earlier and matches first; move it above and exits 1. The bug it points at is real: /home/protocol/transaction/x is caught by /home/:slug* and goes to /learn/protocol/transaction/x (singular, no such page) instead of /learn/protocol/transactions/x. The description's claim that the 26 fragment entries are the only ones on main that fail the check is stale: main has 27. Fix shape: rebase, move the /home/protocol/transaction/:slug* entry above /home/:slug*, re-run the five CI commands on the result.

  2. MEDIUM · docs.json conflict with main · Merge conflicts in 8 hunks, and a "take the PR side" resolution silently deletes live redirects. At least two of the conflicting hunks contain the # entries next to non-fragment exact redirects whose destinations #2367 rewrote (for example /celo-codebase/protocol/proof-of-stake/epoch-rewards/locked-gold-rewards, .../validator-rewards). Failure scenario: a hurried resolution drops those two lines with the fragment entries and two live legacy URLs 404. Fix shape: resolve by taking main's docs.json and removing only entries whose source contains # (1020 redirects become 994; I did this with a few lines of Python and the result passed mint broken-links --check-redirects and the orphan check), then land finding 1's reorder.

  3. MEDIUM · scripts/check_redirects.py:59-71 (Site.__init__), :108-116 (shadow loop) · Only the literal suffix /:slug* is treated as a wildcard; any other pattern is silently treated as an exact source, and duplicate wildcards are not compared. Failure scenarios, each run in a fixture and exiting 0 with "No redirect problems found": (a) /w/:path* then /w/v/:path* (different parameter name) is not flagged as shadowed; (b) /m/:slug*/end then /m/:slug* is accepted; (c) the same wildcard twice (/q/:slug* to two destinations) is accepted although the second never fires. docs.json today uses only /:slug* so nothing is wrong now, but the check passes exactly the class of redirect it exists to catch as soon as someone writes the other documented Mintlify form. Fix shape: in Site.__init__ raise (exit 1) on any source containing : followed by a name or * that is not exactly the /:slug* suffix, and change the shadow test to prefix == earlier_prefix or prefix.startswith(earlier_prefix + "/").

  4. MEDIUM · scripts/check_redirects.py:91-122 (find_problems, nav loop at 117) · A redirect whose source is a real page on disk that navigation does not list is not flagged, and a redirect loop through wildcard destinations is not flagged. I confirmed with mint dev on a fixture that when a redirect source equals a real page, the page is served (/page2 returned 200 with the page body, the redirect never fired), so such a redirect is dead; the script only tests sources against navigation entries. Fixtures: a file hidden.mdx outside navigation with {"source": "/hidden"} passes; /p/:slug* to /q/:slug* and /q/:slug* to /p/:slug* passes (wildcard destinations are deliberately not chained, so the loop is invisible). The current docs.json has no source that is a file (checked on main and on #2368: 0 of 1020 and 0 of 1043). Fix shape: add site.page_exists(source_prefix) to the "source captures a real page" test, not only site.nav; and for wildcard-destination chains add a bounded walk (at most 5 hops) that fails on a repeated source.

  5. LOW · scripts/check_redirects.py:62 (config.get("redirects", [])) · A docs.json whose redirects key is missing or renamed passes: a fixture with "rediects" printed "No redirect problems found" and exited 0. Missing file, bad JSON, missing navigation or a redirect without destination all fail with a traceback and exit 1, which is the right direction. Fix shape: config["redirects"] instead of .get("redirects", []), and fail when redirects or nav is empty.

  6. LOW · .github/workflows/docs-validation.yml:1-20 · The workflow has no permissions: block (the other workflow in the repo declares one), so the job runs with the repository default token scope. Triggers are minimal and safe: pull_request (not pull_request_target), push to main, workflow_dispatch; no secrets; no new dependencies (stdlib Python 3.12 on ubuntu-latest); the new steps run in 0.3 s locally. Fix shape: add permissions: contents: read at the top level.

  7. LOW · scripts/check_redirects.py:117-120 · The nav-link rule fires on #2368's head for the global "Docs for AI" anchor (docs.json:545, href: /build-on-celo/build-with-ai/use-docs-with-ai), which is reported as /build-on-celo/build-with-ai/:slug*: redirects /build-on-celo/build-with-ai/use-docs-with-ai, which navigation links to. The finding is real as a nav hygiene problem (the sidebar link goes through a redirect), but the message is the same as for "source is a real page", and there it is not (the page no longer exists in #2368); an author will look for a page that is not there. Fix shape: split the message: "navigation links to a redirected path; link /build/agents/use-docs-with-ai instead".

Verified good

  • Real semantics, run in mint dev on a fixture (port 3433): exact source below a wildcard wins (/e/one to /alt, /e/two to /dest); of two overlapping wildcards the first in array order wins (/a/b/c went through /a/:slug* to /x/b/c); a wildcard also matches its bare prefix (/bare redirected); a # source never matches (/frag returned 404); a redirect whose source is a real page does not fire (page served). The script encodes the first four and, for the fifth, flags it through navigation. (mint dev shares one client directory with other running sessions; the fixture responses were consistent with the fixture throughout.)
  • It can go red: fixtures for a shadowed wildcard, a chain, a loop (A to B, B to A), a self-redirect, a source equal to a nav page, a wildcard capturing a nav page, a missing destination, a / destination, a # source all exit 1 with a readable line. The wildcard order check and the # check each fail the test suite when removed (mutation run: 1 error each).
  • No false positives on the current file: PR head docs.json yields 0 problems. On origin/main it yields exactly 26 # sources (every fragment entry the PR deletes) plus the /home/protocol/transaction/:slug* shadow from finding 1; on #2368 it yields the same 26 plus the Docs for AI anchor from finding 7 and none of the three dead wildcards the description mentions (that branch has since reordered them). The ? in a destination is flagged as a missing page (/dest?x=1), which is a conservative false positive for a form docs.json does not use.
  • Fails closed on a broken docs.json (invalid JSON, no navigation, a redirect without destination: traceback, exit 1) and on a missing file (traceback); runtime 0.3 s on 1020 redirects; no dependencies beyond the Python standard library; type hints need 3.9+, CI has 3.12.
  • Tests: 14 tests, each pattern has a positive and a negative case, including the two ordering directions, a nav href, and "chain through a wildcard destination is allowed". Weak spot: no test for loops, no test for the CLI exit code or bad input, none for the main path.
  • The deleted fragment entries are correctly judged dead: the description's headless-Chrome check (the bare path wins) matches the /frag result above.

What the PR got right

It encodes a real Mintlify rule (first wildcard wins, exact wins) as an executable check and ships the rule in AGENTS.md next to the redirect procedure, so the rule is where the next editor looks. The check already finds a real routing bug on today's main. The description is candid about what the check cannot see (chains through wildcard destinations, stranded old URLs) and about the PRs it will turn red.

Merge-order hazards

  • This PR conflicts with main in docs.json today (finding 2); a clean pair-merge with #2366 and #2346 (git merge-tree clean) is unaffected.
  • #2368: once this PR is on main, #2368's head still fails the new check (finding 7) and its /build/... handling must be rebased against the check; #2368's docs.json also overlaps this PR's deleted fragment hunks, so whichever lands second resolves docs.json again. Merge this PR first only after finding 1 is fixed, otherwise every PR and main itself fail on the /home/protocol/transaction/:slug* entry. The description's statement that #2367 and #2368 would fail is already half stale: #2367 is merged, and main is the one that fails.
  • The push trigger means a bad merge order turns main red, not just the PR.

@palango

palango commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, all fair. Rebased onto main and pushed. Per finding:

  1. /home/protocol/transaction/:slug* now sits above /home/:slug*, so the check passes on the rebased branch and those URLs reach /learn/protocol/transactions/... again.
  2. I resolved docs.json the way you did: took main's file and cut only the 26 entries with # in the source (1020 to 994). The other redirects are identical to main's, order included, so the epoch-rewards entries are still there.
  3. Any path parameter other than a trailing /:slug* now stops the script with an error and exit 1, and an identical wildcard listed twice is flagged as never matching. Your three fixtures fail now. The parameter check ignores the fragment, because main has a #:~:text= source that should be reported as a # problem rather than abort the run.
  4. Moved to check_redirects.py: flag redirects over unlisted pages and loops through wildcard destinations #2380: the file-source check and a bounded walk for loops through wildcard destinations. Neither case exists in docs.json today.
  5. It reads config["redirects"] and fails with a clear message when redirects or navigation is missing or empty.
  6. Added permissions: contents: read to the workflow.
  7. The message is split. A redirect over a nav page still says so; a navigation link to a redirected path now reads /x: navigation links to this path, which <source> redirects; link <destination> instead.

Each has unit tests (25, up from 14). Against main the check reports exactly the 27 problems this PR fixes, and against #2368's head the 26 fragment sources plus the Docs for AI anchor. The description is updated to match.

@GigaHierz GigaHierz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head ccf6536 · fetch matched headRefOid; main 7b8f97c (#2368 layout); merge-tree CONFLICTS in docs.json only; trial merge (main's docs.json minus the 26 # sources, 1043 to 1017) run through the check: exits 1 with 1 problem (the "Docs for AI" anchor); with that anchor fixed: 0 problems, 25/25 tests, validate, broken-links --check-redirects and check-orphans pass; origin/main as it stands: 27 problems; CI on head green (all 8 steps)

Verdict: REQUEST-CHANGES

Status of earlier findings

  1. HIGH, /home/protocol/transaction/:slug* shadowed by /home/:slug*: RESOLVED ON MAIN, and the PR's reorder commit is now redundant. #2368 already put the wildcards most specific first, so the check reports no shadowed wildcard on current main. On the trial merge the PR's docs.json reorder commit (56aabb4) must be dropped, not re-applied (see rebase work).
  2. MEDIUM, a "take the PR side" resolution deletes live redirects: ADDRESSED in the PR comment (resolved as main's file minus the # entries). Re-done independently against the new main: main has 1043 redirects, 26 with #, 1017 remain; the resulting docs.json differs from main by exactly 104 deleted lines (26 four-line entries) and nothing else.
  3. MEDIUM, non-/:slug* patterns treated as exact sources, duplicate wildcards not compared: FIXED. Fixtures (below): /w/:path*, /w/:id, /m/:slug*/end, /m/* each exit 1 with "Cannot check redirects"; the same wildcard listed twice exits 1 ("never matches, because the same wildcard comes earlier"). A #:~:text= source is reported as a # problem instead of aborting.
  4. MEDIUM, a redirect whose source is a real page outside navigation is not flagged, and loops through wildcard destinations pass: DEFERRED to #2380 (open, filed, linked in the PR body with unit-test requirements). Still true on the head: both fixtures exit 0. Accepted as out of scope for this PR because neither case exists in docs.json today (checked on the trial merge: 0 of 1017 sources are page files; 0 wildcard self-loops; 0 two-wildcard cycles). One addition for #2380: the single-hop self-loop /p/:slug* to /p/:slug* also exits 0 and is the cheapest case to catch.
  5. LOW, missing redirects key passes: FIXED. rediects (typo) and an empty redirects exit 1 with a message; a redirect with no destination exits 1 with a traceback (fails closed).
  6. LOW, no permissions: block: FIXED. The workflow has top-level permissions: contents: read.
  7. LOW, ambiguous nav-link message: FIXED. A redirect over a nav page and a navigation link to a redirected path now read differently, the second naming the destination: "navigation links to this path, which redirects; link instead".

New findings

  1. MEDIUM · docs.json:545 (global "Docs for AI" anchor, "href": "/build-on-celo/build-with-ai/use-docs-with-ai"; present on current main, not in the PR diff) · The PR cannot land green on current main: the check correctly fails on this anchor, which #2368 left pointing at the old path, and the workflow also runs on push to main. Failure scenario (run): the trial merge exits 1 with /build-on-celo/build-with-ai/use-docs-with-ai: navigation links to this path, which /build-on-celo/build-with-ai/:slug* redirects; link /build/agents/use-docs-with-ai instead; merging the PR with a plain docs.json conflict resolution turns the "Check redirects" step red on the PR and on main. Fix shape: in the rebase, change that one href to /build/agents/use-docs-with-ai (after which the trial merge reports "No redirect problems found." and mint validate and mint broken-links --check-redirects pass).
  2. LOW · PR body and commit 56aabb4 · The description no longer matches main. It says main has the /home/protocol/transaction/:slug* shadow and that the PR "fixes all 27 problems ... moves the transaction wildcard", and that #2368 will fail the check. On current main the 27 problems are the 26 # sources plus the Docs for AI anchor; #2368 is merged; and the chain example /build/:slug* to /build-on-celo/:slug* now runs the other way (/build-on-celo/:slug* to /build/:slug*). The body is the squash commit record, so a reader would look for a reorder that is not in the diff. Fix shape: after the rebase, drop the reorder commit, update the body to "26 # sources and the Docs for AI anchor", and correct the chain example.

Verified good

Setup: PR head ccf6536 equals the API head. The PR's own diff against its merge base is 6 files (workflow, AGENTS.md, docs.json, scripts/README.md, check_redirects.py, test_check_redirects.py).

  • Run on current origin/main's docs.json (script from the PR head): exit 1, 27 problems = 26 sources containing # (identical to the entries the PR deletes) plus the Docs for AI nav link. No shadowed wildcard, chain, loop or nav-capture flagged, so #2368's reorder left no dead wildcard.
  • Run on the trial merge: exit 1, 1 problem (finding 1); with the anchor edited: exit 0, "No redirect problems found." On the PR head's own docs.json: exit 0.
  • Fixtures in a temp copy (synthetic site per case, /tmp/reviews3/fixtures.py), colour is the exit code:
    • RED: shadowed wildcard; chain (/x to /y to /a); exact loop (/x and /y); self redirect; source equal to a nav page; wildcard capturing a nav page; duplicate wildcard; missing redirects key; empty redirects; /w/:path*, /w/:id, /m/:slug*/end, /m/*; # source; destination page missing; destination not in nav; wildcard destination whose base is missing; nav href that a redirect catches; redirect without destination (traceback).
    • GREEN, as documented and deferred to #2380: source equal to a real page outside navigation; loop through wildcard destinations (/p/:slug* and /q/:slug* pointing at each other); single-hop wildcard self-loop.
    • GREEN, untested semantics, no claim made: a source with ?, case-differing duplicates, a trailing-slash source, an external destination.
  • Tests: python3 -m unittest discover -s scripts -p 'test_*.py' runs 25 tests, OK in 0.04 s (pytest is not installed locally and is not needed). Mutation run on a temp copy: removing each of ten rules (shadow check, duplicate check, # check, nav-capture check, chain check, parameter refusal, empty-config refusal, nav-link message, exact-beats-wildcard, bare-prefix match) fails at least one test, so the suite goes red when a rule breaks. Weak spot: no test for the CLI exit code on a traceback path (missing destination), which still fails closed.
  • Fail-closed direction: invalid JSON, missing or empty redirects/navigation, any path parameter other than a trailing /:slug*, and a redirect without destination all exit 1; nothing passes silently.
  • Runtime and dependencies: 0.03 s user time on 1017 redirects; standard library only; type hints need Python 3.9+; the workflow has no setup-python step and uses the runner's python3 (the CI steps passed; local run was 3.12.1).
  • Workflow: triggers are pull_request to main (not pull_request_target), push to main, workflow_dispatch; top-level permissions: contents: read; no secrets referenced; the two new steps run after the existing Mintlify steps, so they do not mask a docs.json schema error.
  • Rendering is not applicable (no page content changed), so no mint dev pass.
  • mint validate, mint broken-links --check-redirects and bash scripts/check-orphans.sh on the trial merge: pass (broken-links and orphans pass with and without the anchor fix).
  • The deleted fragment entries are dead: a # source can never match, which the fixture confirms (RED) and the PR's headless-Chrome check supports.
  • AGENTS.md: the added redirect rules (no #, trailing /:slug* only, narrower wildcard above broader) and the new command line merge cleanly into main's AGENTS.md; scripts/README.md describes the script and states its limits.

What the PR got right

It turns a real Mintlify rule (exact beats wildcard, first wildcard wins, # never matches) into an executable check, ships the rule next to the redirect procedure in AGENTS.md, and states its own blind spots in the body and in a filed issue (#2380). Every earlier finding was fixed with a commit and unit tests or filed. The check already flags a real leftover on today's main, which is the proof it can go red on the real file.

Merge-order hazards and exact rebase work

  • git merge-tree --write-tree origin/main refs/remotes/pr/2369: conflicts in docs.json only; AGENTS.md and scripts/README.md auto-merge. The conflict is the layout move (#2368 rewrote the redirects block), a rebase task and not a content defect.
  • Exact rebase work for the author, in one pass:
    1. Take main's docs.json, then delete every redirects entry whose source contains # (26 entries, 1043 to 1017); keep every other entry in place and in order. Drop the PR's own reorder commit (56aabb4), because main already has the wildcards in the right order.
    2. Change the global anchor "href": "/build-on-celo/build-with-ai/use-docs-with-ai" (docs.json:545) to /build/agents/use-docs-with-ai.
    3. Run python3 scripts/check_redirects.py (expect "No redirect problems found."), the unit tests, mint validate, mint broken-links --check-redirects and bash scripts/check-orphans.sh, and update the PR body (finding 2).
  • The check also runs on push to main, so landing this PR with step 2 missing turns main red on the "Check redirects" step. Any PR that moves a page and leaves a nav href on a redirected path will now fail the same way.
  • Other PRs touching the redirects block will conflict with the 26 deletions; whichever merges second re-resolves docs.json by the same rule.
  • Follow-up: #2380 (open) tracks the two deferred gaps; add the single-hop wildcard self-loop to it.

palango added 10 commits October 7, 2026 07:09
Catches fragment sources, wildcards shadowed by an earlier wildcard,
sources that capture navigation, and chained or off-nav destinations.
Browsers never send the fragment, so these never matched. Checked in
headless Chrome: /celo-gold-holder-guide/ledger#install-the-celo-cli
lands on the bare path's destination, not this entry's.
Documents the check and the two rules it enforces in AGENTS.md.
It treated any source without a trailing /:slug* as exact, so /a/:path* was
never compared with other wildcards, and a missing redirects key passed.
The later copy never fires, but the shadow test only compared a wildcard
with strictly broader ones.
The old message read like a redirect capturing a real page, so authors went
looking for a page that no longer exists instead of fixing the nav link.
The job only checks out and reads files, so it does not need the
repository's default token scope.
Authors should know to write wildcards as /:slug* before CI tells them.
A #:~:text= source on main stopped the check with an error instead of
being reported as a # source.
The anchor still linked the pre-move path, which a wildcard redirect
catches, so the redirect check failed on it.
@palango
palango force-pushed the palango/redirect-check branch from ccf6536 to d7c0fbd Compare October 7, 2026 05:14
@palango

palango commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. Rebased onto main (7f5d9bb) and pushed.

  1. Docs for AI anchor: fixed in d7c0fbd. The anchor now links /build/agents/use-docs-with-ai, and python3 scripts/check_redirects.py reports no problems on the rebased tree.
  2. Stale description and reorder commit: I dropped the reorder commit in the rebase, since docs: merge Build on Celo and Tooling into one Build tab #2368 already put the transaction wildcard above /home/:slug*. docs.json is main's file minus the 26 entries with # in the source (1043 to 1017, 104 deleted lines), plus the one anchor line. The description now counts the 26 # sources and the anchor, drops the reorder, and has the chain example the right way round (/build-on-celo/:slug* to /build/:slug*).

I added the single-hop self-loop to #2380. On this branch /p/:slug* to /p/:slug* still passes, as you found.

Unit tests (25), the redirect check, mint validate, mint broken-links --check-redirects and the orphan check all pass locally on mint 4.2.746.

@palango
palango requested a review from GigaHierz October 7, 2026 05:14

@GigaHierz GigaHierz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head d7c0fbd · re-verified against main c725175: python3 scripts/check_redirects.py reports "No redirect problems found.", all 25 unit tests pass, mint validate, mint broken-links --check-redirects and scripts/check-orphans.sh pass, CI green.

Verdict: APPROVE

All earlier findings are fixed or filed:

  • The Docs for AI anchor now points at /build/agents/use-docs-with-ai; the check passes on the whole tree, so merging does not turn main red.
  • docs.json is main's redirects minus the 26 sources that contain #, order preserved, plus the one anchor line (1 insertion, 105 deletions). The reorder commit is gone and the body matches the head.
  • The workflow uses pull_request with permissions: contents: read and no secrets.
  • I ran my own fixtures in a temp copy: the check goes red for a shadowed wildcard, a chain, a loop, a source equal to a nav page, a duplicate wildcard, a missing redirects key, a wildcard with a non-/:slug* parameter and a # source. The cases deferred to #2380 (a source that is a real page outside navigation, loops through wildcard destinations, the single-hop wildcard self-loop) still pass, as the body says, and none exists in docs.json today.
  • My one remaining note was that the #2380 text wrote the example wildcard chain backwards. I corrected that sentence in the issue (/build-on-celo/:slug* to /build/:slug*), so nothing is left open on this PR.

Merge order with #2382, which changes the same anchor line to the same value: either order merges cleanly and gives the same docs.json; whichever lands second is a no-op on that line.

@GigaHierz
GigaHierz merged commit 418506f into main Oct 7, 2026
5 checks passed
@GigaHierz
GigaHierz deleted the palango/redirect-check branch October 7, 2026 07:27
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.

2 participants