From bb3a7015119db926bc44ac928866963d15839718 Mon Sep 17 00:00:00 2001 From: Esha Noronha Date: Fri, 29 Aug 2025 12:18:14 +0200 Subject: [PATCH 01/15] Added lychee config files --- .github/lychee.toml | 84 ++++++++++++++++++++++++++++ .github/workflows/check-pr-links.yml | 34 +++++++++++ .gitignore | 1 + .lycheeignore | 32 +++++++++++ 4 files changed, 151 insertions(+) create mode 100644 .github/lychee.toml create mode 100644 .github/workflows/check-pr-links.yml create mode 100644 .lycheeignore diff --git a/.github/lychee.toml b/.github/lychee.toml new file mode 100644 index 00000000000..4962f3ab5f5 --- /dev/null +++ b/.github/lychee.toml @@ -0,0 +1,84 @@ +# .github/lychee.toml + +############################# Display ############################# +# Verbose program output +# Accepts log level: "error", "warn", "info", "debug", "trace" +verbose = "info" + +# Don't show interactive progress bar while checking links. +no_progress = false + +############################# Cache ############################### +# Enable link caching. This can be helpful to avoid checking the same links on +# multiple runs. +cache = true + +# Discard all cached requests older than this duration. +max_cache_age = "1d" + +############################# Runtime ############################# +# Maximum number of concurrent link checks. +max_concurrency = 12 + +# Maximum number of allowed redirects. +max_redirects = 10 + +# Maximum number of allowed retries before a link is declared dead. +max_retries = 5 + +############################# Requests ############################ +# Website timeout from connect to response finished. +timeout = 60 + +# Minimum wait time in seconds between retries of failed requests. +retry_wait_time = 3 + +# Accept more status codes (follow redirects automatically) +accept = ["200..=204", "301..=308", "429"] + +# Avoid false fragment errors +include_fragments = false + +# Only test links with the given schemes (e.g. https). +# Omit to check links with any other scheme. +# At the moment, we support http, https, file, and mailto. +scheme = ["https"] + +# When links are available using HTTPS, treat HTTP links as errors. +require_https = false + +# Fallback extensions to apply when a URL does not specify one. +# This is common in documentation tools that cross-reference files without extensions. +fallback_extensions = ["md", "html"] + +############################# Exclusions ########################## +# Check links inside `` and `
` blocks as well as Markdown code
+# blocks.
+include_verbatim = false
+
+# Ignore case of paths when matching glob patterns.
+glob_ignore_case = false
+
+# Exclude URLs and mail addresses from checking (supports regex).
+exclude = [
+  '^mailto:',
+  '^https?://localhost',
+  '^https?://127\\.0\\.0\\.1',
+  '^https://www\.linkedin\.com',
+  '^https?://web\\.archive\\.org/web/'
+]
+
+# Exclude these filesystem paths from getting checked.
+exclude_path = [
+  '(^|/)node_modules/',
+  '(^|/)dist/',
+  '(^|/)bin/',
+  '\\.txt$',      # skip .txt extensions
+  '(^|/)test/'   # skip directories named "test"
+]
+
+# URLs to check (supports regex). Has preference over all excludes.
+include = ['gist\.github\.com.*']
+
+# Skip checking mail addresses
+include_mail = true
\ No newline at end of file
diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
new file mode 100644
index 00000000000..9fcad182341
--- /dev/null
+++ b/.github/workflows/check-pr-links.yml
@@ -0,0 +1,34 @@
+name: Check Links In Pull Requests
+
+on: [pull_request]
+
+jobs:
+  check-links:
+    runs-on: ubuntu-latest
+    steps:
+    # Step 1: Checkout the repo
+      - name: Checkout GitHub repo
+        uses: actions/checkout@v4
+
+    # Step 2: Run Lychee to check links
+      - name: Run Lychee link checker
+        uses: lycheeverse/lychee-action@v2
+        with:
+          args: |
+            --no-progress
+            --include-fragments
+
+    # Step 3: Provide a helpful message if the check fails
+      - name: Helpful failure message
+        if: ${{ failure() }}
+        run: |
+          echo "::error::Link check failed! Please review the broken links reported above."
+          echo ""
+          echo "If certain links are valid but fail due to:"
+          echo "- CAPTCHA challenges"
+          echo "- IP blocking"
+          echo "- Authentication requirements"
+          echo "- Rate limiting"
+          echo ""
+          echo "Consider adding them to .lycheeignore to bypass future checks."
+          echo "Format: Add one URL pattern per line"
diff --git a/.gitignore b/.gitignore
index 655ba93439d..946cb65a602 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 *.orig
 .vscode
 .idea
+.lycheecache
diff --git a/.lycheeignore b/.lycheeignore
new file mode 100644
index 00000000000..ecec8ea905e
--- /dev/null
+++ b/.lycheeignore
@@ -0,0 +1,32 @@
+# These links are ignored by lychee link checker: https://github.com/lycheeverse/lychee
+# The file allows you to list multiple regular expressions for exclusion (one pattern per line).
+# The `.lycheeignore` file is only used for excluding URLs, not paths. Use the `exclude_path` key in the `lychee.toml` file. ref: https://lychee.cli.rs/recipes/excluding-paths/
+
+# GitHub blob/tree fragment links
+^https://github\.com/umbraco/Umbraco-CMS/blob/.*/.*#L.*
+^https://github\.com/umbraco/Umbraco-CMS/tree/.*
+^https://github\.com/Shazwazza/Articulate/blob/.*/.*#L.*
+^https://github\.com/umbraco/Umbraco-CMS/blob/.*
+
+# Anchor/fragment links causing false positives
+^https://docs\.umbraco\.com/.*/#.*
+^https://apidocs\.umbraco\.com/.*/#.*
+^https://tinymce\.github\.io/.*/#.*
+^https://openid\.net/.*/#.*
+^https://docs\.microsoft\.com/.*#.*
+^https://learn\.microsoft\.com/.*#.*
+^https://developer\.mozilla\.org/.*/#.*
+^https://learning\.postman\.com/docs/.*/#.*
+^https://nginx\.org/.*/#.*
+^https://azure\.microsoft\.com/en-gb/services/media-services/.*
+^https://www\.tiny\.cloud/docs/.*
+
+# TinyMCE anchors
+^https://github\.com/tinymce/tinymce/issues/.*#.*
+
+# NIST FIPS and other static docs
+^https://csrc\.nist\.gov/publications/PubsFIPS\.html#.*
+
+# Timeout-prone Umbraco issue links
+^https://issues\.umbraco\.org/issue/.*
+^https://issues\.umbraco\.org/issues/.*

From 76694aae7b73e1d92559b25207f17002802d9d42 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 12:39:08 +0200
Subject: [PATCH 02/15] Fix broken links

---
 16/umbraco-cms/fundamentals/code/source-control.md              | 2 +-
 .../upgrading/version-specific/upgrade-from-8-to-latest.md      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/16/umbraco-cms/fundamentals/code/source-control.md b/16/umbraco-cms/fundamentals/code/source-control.md
index 7b78737f58a..65dabbd64fb 100644
--- a/16/umbraco-cms/fundamentals/code/source-control.md
+++ b/16/umbraco-cms/fundamentals/code/source-control.md
@@ -8,7 +8,7 @@ description: >-
 
 ## Umbraco Cloud
 
-When you are running your site on Umbraco Cloud, source control is a part of the experience. Have a look at the ['Technical overview of an Umbraco Cloud Environment'](https://docs.umbraco.com/umbraco-cloud/getting-started/environments) and the information on ['Working with your Umbraco Cloud project'](https://docs.umbraco.com/umbraco-cloud/setup/set-up#working-with-your-umbraco-cloud-project) for a steer on Source/Version Control good practices.
+When you are running your site on Umbraco Cloud, source control is a part of the experience. Have a look at the ['Technical overview of an Umbraco Cloud Environment'](https://docs.umbraco.com/umbraco-cloud/getting-started/environments) and the information on ['Working with a Local Clone'](https://docs.umbraco.com/umbraco-cloud/build-and-customize-your-solution/handle-deployments-and-environments/working-locally) for a steer on Source/Version Control good practices.
 
 ## Outside of Umbraco Cloud
 
diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
index 8da11efdc80..49da82b82c6 100644
--- a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
+++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
@@ -43,7 +43,7 @@ A video tutorial guiding you through the steps of upgrading from version 8 to th
 If you use Umbraco Forms, make sure to have [`StoreUmbracoFormsInDbset`](https://docs.umbraco.com/umbraco-forms/developer/forms-in-the-database#enable-storing-forms-definitions-in-the-database)to `True` before **step 1**.
 {% endhint %}
 
-1. Create a backup of the database from your Umbraco 8 project (after you have upgraded to the latest version of v8). For this, you can use the [database backup guide](https://docs.umbraco.com/umbraco-cloud/databases/backups#backup-with-sql-server-management-studio).
+1. Create a backup of the database from your Umbraco 8 project (after you have upgraded to the latest version of v8). For this, you can use the [Database backups Guide](https://docs.umbraco.com/umbraco-cloud/build-and-customize-your-solution/set-up-your-project/databases/backups#restoring-a-cloud-backup-to-a-sql-server-database).
 2. Import the database backup into SQL Server Management Studio.
 3. Update the connection string in the new projects `appsettings.json` file so that it connects to the Umbraco 8 database:
 

From e9d1667184d4c6cc56c1179fc5b78e2e9892a258 Mon Sep 17 00:00:00 2001
From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com>
Date: Fri, 29 Aug 2025 12:45:01 +0200
Subject: [PATCH 03/15] Update
 16/umbraco-cms/fundamentals/code/source-control.md

---
 16/umbraco-cms/fundamentals/code/source-control.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/16/umbraco-cms/fundamentals/code/source-control.md b/16/umbraco-cms/fundamentals/code/source-control.md
index 65dabbd64fb..2da8a96152a 100644
--- a/16/umbraco-cms/fundamentals/code/source-control.md
+++ b/16/umbraco-cms/fundamentals/code/source-control.md
@@ -8,7 +8,7 @@ description: >-
 
 ## Umbraco Cloud
 
-When you are running your site on Umbraco Cloud, source control is a part of the experience. Have a look at the ['Technical overview of an Umbraco Cloud Environment'](https://docs.umbraco.com/umbraco-cloud/getting-started/environments) and the information on ['Working with a Local Clone'](https://docs.umbraco.com/umbraco-cloud/build-and-customize-your-solution/handle-deployments-and-environments/working-locally) for a steer on Source/Version Control good practices.
+When you are running your site on Umbraco Cloud, source control is a part of the experience. Have a look at the ['Technical overview of an Umbraco Cloud Environment'](https://docs.umbraco.com/umbraco-cloud/getting-started/environments). Additionally, look at ['Working with a Local Clone'](https://docs.umbraco.com/umbraco-cloud/build-and-customize-your-solution/handle-deployments-and-environments/working-locally) for a steer on Source/Version Control good practices.
 
 ## Outside of Umbraco Cloud
 

From 3d75ce290e3c964063fc3f59a2dcaecfc445367a Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 12:48:43 +0200
Subject: [PATCH 04/15] updated args

---
 .github/workflows/check-pr-links.yml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 9fcad182341..8a3615185d0 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -14,9 +14,7 @@ jobs:
       - name: Run Lychee link checker
         uses: lycheeverse/lychee-action@v2
         with:
-          args: |
-            --no-progress
-            --include-fragments
+          args: "--no-progress --include-fragments 16/**/*.md"
 
     # Step 3: Provide a helpful message if the check fails
       - name: Helpful failure message

From db2586d486244902c2b31f7150dfdd7cd6737dfd Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 12:56:58 +0200
Subject: [PATCH 05/15] Updated config file from lychee docs

---
 .github/workflows/check-pr-links.yml | 63 ++++++++++++++++++++++++----
 1 file changed, 54 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 8a3615185d0..88ed8481a7a 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -1,24 +1,68 @@
 name: Check Links In Pull Requests
 
-on: [pull_request]
+on:
+  pull_request:
+    branches:
+      - main
+    # Optionally limit the check to certain file types
+    # paths:
+    #   - '**/*.md'
+    #   - '**/*.html'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
+    
     steps:
-    # Step 1: Checkout the repo
-      - name: Checkout GitHub repo
+      - name: Clone repository
         uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Check out base branch
+        run: git checkout ${{github.event.pull_request.base.ref}}
+
+      - name: Dump all links from ${{github.event.pull_request.base.ref}}
+        uses: lycheeverse/lychee-action@v2
+        with:
+          args: |
+            --dump
+            --include-fragments
+            .
+          output: ./existing-links.txt
+        continue-on-error: true # Don't fail if base branch check has issues
+
+      - name: Stash untracked files
+        run: git stash push --include-untracked
+
+      - name: Check out feature branch
+        run: git checkout ${{ github.head_ref }}
+
+      - name: Apply stashed changes
+        run: git stash pop || true
+
+      - name: Update ignore file
+        run: |
+          if [ -f "existing-links.txt" ]; then
+            cat existing-links.txt >> .lycheeignore
+          fi
 
-    # Step 2: Run Lychee to check links
-      - name: Run Lychee link checker
+      - name: Check links
         uses: lycheeverse/lychee-action@v2
         with:
-          args: "--no-progress --include-fragments 16/**/*.md"
+          args: |
+            --no-progress
+            --include-fragments
+            .
 
-    # Step 3: Provide a helpful message if the check fails
-      - name: Helpful failure message
-        if: ${{ failure() }}
+      - name: Provide helpful failure message
+        if: failure()
         run: |
           echo "::error::Link check failed! Please review the broken links reported above."
           echo ""
@@ -30,3 +74,4 @@ jobs:
           echo ""
           echo "Consider adding them to .lycheeignore to bypass future checks."
           echo "Format: Add one URL pattern per line"
+          exit 1
\ No newline at end of file

From 10aad2e8c05feca8d854367f953287a849df791c Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 13:04:25 +0200
Subject: [PATCH 06/15] Other try

---
 .github/workflows/check-pr-links.yml | 75 +++-------------------------
 1 file changed, 7 insertions(+), 68 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 88ed8481a7a..bf5a9c69ca9 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -1,77 +1,16 @@
-name: Check Links In Pull Requests
+name: Check Links
 
 on:
   pull_request:
-    branches:
-      - main
-    # Optionally limit the check to certain file types
-    # paths:
-    #   - '**/*.md'
-    #   - '**/*.html'
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: true
+    types: [opened, synchronize, reopened]
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
-    
     steps:
-      - name: Clone repository
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-          ref: ${{github.event.pull_request.head.ref}}
-          repository: ${{github.event.pull_request.head.repo.full_name}}
-
-      - name: Check out base branch
-        run: git checkout ${{github.event.pull_request.base.ref}}
-
-      - name: Dump all links from ${{github.event.pull_request.base.ref}}
-        uses: lycheeverse/lychee-action@v2
+      - uses: actions/checkout@v4
+      - uses: lycheeverse/lychee-action@v2
+      - name: Comment Broken Links
+        uses: marocchino/sticky-pull-request-comment@v2
         with:
-          args: |
-            --dump
-            --include-fragments
-            .
-          output: ./existing-links.txt
-        continue-on-error: true # Don't fail if base branch check has issues
-
-      - name: Stash untracked files
-        run: git stash push --include-untracked
-
-      - name: Check out feature branch
-        run: git checkout ${{ github.head_ref }}
-
-      - name: Apply stashed changes
-        run: git stash pop || true
-
-      - name: Update ignore file
-        run: |
-          if [ -f "existing-links.txt" ]; then
-            cat existing-links.txt >> .lycheeignore
-          fi
-
-      - name: Check links
-        uses: lycheeverse/lychee-action@v2
-        with:
-          args: |
-            --no-progress
-            --include-fragments
-            .
-
-      - name: Provide helpful failure message
-        if: failure()
-        run: |
-          echo "::error::Link check failed! Please review the broken links reported above."
-          echo ""
-          echo "If certain links are valid but fail due to:"
-          echo "- CAPTCHA challenges"
-          echo "- IP blocking"
-          echo "- Authentication requirements"
-          echo "- Rate limiting"
-          echo ""
-          echo "Consider adding them to .lycheeignore to bypass future checks."
-          echo "Format: Add one URL pattern per line"
-          exit 1
\ No newline at end of file
+          path: lychee/out.md
\ No newline at end of file

From 99cd3f112467039140a1a7c6fff1af85f16a4bd9 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 14:08:05 +0200
Subject: [PATCH 07/15] Updated GitHub actions to run only on changed markdown
 files

---
 .github/workflows/check-pr-links.yml | 73 +++++++++++++++++++++++++---
 1 file changed, 66 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index bf5a9c69ca9..6eb7bb4b4e3 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -1,16 +1,75 @@
-name: Check Links
+name: Check Links In Pull Requests
 
 on:
   pull_request:
-    types: [opened, synchronize, reopened]
+    branches:
+      - main
+    paths:
+      - '**/*.md'   # Only trigger workflow if any Markdown files change
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
-      - uses: lycheeverse/lychee-action@v2
-      - name: Comment Broken Links
-        uses: marocchino/sticky-pull-request-comment@v2
+      # Step 1: Checkout the repository
+      - name: Checkout GitHub repo
+        uses: actions/checkout@v4
         with:
-          path: lychee/out.md
\ No newline at end of file
+          fetch-depth: 0
+
+      # Step 2: Check out base branch and dump all links (no fragments)
+      - name: Check out base branch
+        run: git checkout ${{ github.event.pull_request.base.ref }}
+
+      - name: Dump all links from base branch (no fragments)
+        uses: lycheeverse/lychee-action@v2
+        with:
+          args: "--dump ."
+          output: ./existing-links.txt
+        continue-on-error: true
+
+      # Step 3: Stash untracked files and switch back to feature branch
+      - name: Stash untracked files
+        run: git stash push --include-untracked
+
+      - name: Check out feature branch
+        run: git checkout ${{ github.head_ref }}
+
+      - name: Apply stashed changes
+        run: git stash pop || true
+
+      # Step 4: Add base branch links to .lycheeignore
+      - name: Update ignore file
+        run: |
+          if [ -f "existing-links.txt" ]; then
+            cat existing-links.txt >> .lycheeignore
+          fi
+
+      # Step 5: Get changed Markdown files in the PR
+      - name: Get changed Markdown files
+        id: changed-files
+        run: |
+          files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
+          echo "changed_files=$files" >> $GITHUB_ENV
+
+      # Step 6: Run Lychee on changed files only (with fragments)
+      - name: Run Lychee link checker
+        uses: lycheeverse/lychee-action@v2
+        with:
+          args: "--no-progress --include-fragments ${{ env.changed_files }}"
+
+      # Step 7: Provide a helpful failure message
+      - name: Helpful failure message
+        if: ${{ failure() }}
+        run: |
+          echo "::error::Link check failed! Please review the broken links reported above."
+          echo ""
+          echo "If certain links are valid but fail due to:"
+          echo "- CAPTCHA challenges"
+          echo "- IP blocking"
+          echo "- Authentication requirements"
+          echo "- Rate limiting"
+          echo ""
+          echo "Consider adding them to .lycheeignore to bypass future checks."
+          echo "Format: Add one URL pattern per line"
+          exit 1

From c95f1b396cb79a81e5abda28cff2674e430c9770 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 14:13:59 +0200
Subject: [PATCH 08/15] Fixed filenames format

---
 .github/workflows/check-pr-links.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 6eb7bb4b4e3..b12bd7820f0 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -50,10 +50,12 @@ jobs:
         id: changed-files
         run: |
           files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
+          files=$(echo "$files" | tr '\n' ' ')
           echo "changed_files=$files" >> $GITHUB_ENV
 
       # Step 6: Run Lychee on changed files only (with fragments)
       - name: Run Lychee link checker
+        if: ${{ env.changed_files != '' }}
         uses: lycheeverse/lychee-action@v2
         with:
           args: "--no-progress --include-fragments ${{ env.changed_files }}"

From a891e8b7a0310f15da08dece53db756fc9bcfea7 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Fri, 29 Aug 2025 14:21:27 +0200
Subject: [PATCH 09/15] Updated to solves the regex explosion issue

---
 .github/workflows/check-pr-links.yml | 51 +++++++++++++++++-----------
 1 file changed, 32 insertions(+), 19 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index b12bd7820f0..87bfd313a78 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -17,18 +17,38 @@ jobs:
         with:
           fetch-depth: 0
 
-      # Step 2: Check out base branch and dump all links (no fragments)
+      # Step 2: Check out base branch
       - name: Check out base branch
         run: git checkout ${{ github.event.pull_request.base.ref }}
 
-      - name: Dump all links from base branch (no fragments)
+      # Step 3: Get changed Markdown files
+      - name: Get changed Markdown files
+        id: changed-files
+        run: |
+          mapfile -t files_array < <(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
+          files=""
+          for f in "${files_array[@]}"; do
+            files="$files \"$f\""
+          done
+          echo "changed_files=$files" >> $GITHUB_ENV
+          echo "Changed Markdown files: $files"
+
+      # Step 4: Skip workflow if no Markdown files changed
+      - name: Skip if no Markdown files changed
+        if: ${{ env.changed_files == '' }}
+        run: |
+          echo "No Markdown files changed in this PR. Skipping link check."
+          exit 0
+
+      # Step 5: Dump base branch links for changed files only (no fragments)
+      - name: Dump base branch links
         uses: lycheeverse/lychee-action@v2
         with:
-          args: "--dump ."
+          args: "--dump ${{ env.changed_files }}"
           output: ./existing-links.txt
         continue-on-error: true
 
-      # Step 3: Stash untracked files and switch back to feature branch
+      # Step 6: Stash untracked files and switch back to feature branch
       - name: Stash untracked files
         run: git stash push --include-untracked
 
@@ -38,29 +58,22 @@ jobs:
       - name: Apply stashed changes
         run: git stash pop || true
 
-      # Step 4: Add base branch links to .lycheeignore
+      # Step 7: Add base branch links to .lycheeignore
       - name: Update ignore file
         run: |
           if [ -f "existing-links.txt" ]; then
             cat existing-links.txt >> .lycheeignore
           fi
 
-      # Step 5: Get changed Markdown files in the PR
-      - name: Get changed Markdown files
-        id: changed-files
-        run: |
-          files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
-          files=$(echo "$files" | tr '\n' ' ')
-          echo "changed_files=$files" >> $GITHUB_ENV
-
-      # Step 6: Run Lychee on changed files only (with fragments)
+      # Step 8: Run Lychee on changed files one by one
       - name: Run Lychee link checker
-        if: ${{ env.changed_files != '' }}
-        uses: lycheeverse/lychee-action@v2
-        with:
-          args: "--no-progress --include-fragments ${{ env.changed_files }}"
+        run: |
+          for f in ${{ env.changed_files }}; do
+            echo "Checking links in $f"
+            lychee --no-progress --include-fragments "$f"
+          done
 
-      # Step 7: Provide a helpful failure message
+      # Step 9: Provide a helpful failure message
       - name: Helpful failure message
         if: ${{ failure() }}
         run: |

From 7e55b71de828f83649a97ef4c8f595ac023d00d6 Mon Sep 17 00:00:00 2001
From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com>
Date: Fri, 29 Aug 2025 14:42:58 +0200
Subject: [PATCH 10/15] Update .lycheeignore

---
 .lycheeignore | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.lycheeignore b/.lycheeignore
index ecec8ea905e..c149333be85 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -9,7 +9,6 @@
 ^https://github\.com/umbraco/Umbraco-CMS/blob/.*
 
 # Anchor/fragment links causing false positives
-^https://docs\.umbraco\.com/.*/#.*
 ^https://apidocs\.umbraco\.com/.*/#.*
 ^https://tinymce\.github\.io/.*/#.*
 ^https://openid\.net/.*/#.*

From d64c247ddd400f91dbd13d31e8907de2337d8d94 Mon Sep 17 00:00:00 2001
From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com>
Date: Fri, 29 Aug 2025 14:57:02 +0200
Subject: [PATCH 11/15] Updated lychee.toml

---
 .github/lychee.toml | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/.github/lychee.toml b/.github/lychee.toml
index 4962f3ab5f5..37c5817e35f 100644
--- a/.github/lychee.toml
+++ b/.github/lychee.toml
@@ -81,4 +81,9 @@ exclude_path = [
 include = ['gist\.github\.com.*']
 
 # Skip checking mail addresses
-include_mail = true
\ No newline at end of file
+include_mail = true
+
+############################# Content Checks ######################
+# Mark pages as broken if the body contains "page not found" or "404"
+[content]
+deny = ["(?i)page not found", "(?i)404"]

From 254ddc0cb2ee2c228ee981ae1097c8b9bb704025 Mon Sep 17 00:00:00 2001
From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com>
Date: Fri, 29 Aug 2025 14:58:57 +0200
Subject: [PATCH 12/15] Updated check-pr-links.yml

---
 .github/workflows/check-pr-links.yml | 78 +++++++++++-----------------
 1 file changed, 30 insertions(+), 48 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 87bfd313a78..ad4354b32ee 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -5,23 +5,18 @@ on:
     branches:
       - main
     paths:
-      - '**/*.md'   # Only trigger workflow if any Markdown files change
+      - '**/*.md'
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
     steps:
-      # Step 1: Checkout the repository
       - name: Checkout GitHub repo
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
-      # Step 2: Check out base branch
-      - name: Check out base branch
-        run: git checkout ${{ github.event.pull_request.base.ref }}
-
-      # Step 3: Get changed Markdown files
+      # Get changed Markdown files
       - name: Get changed Markdown files
         id: changed-files
         run: |
@@ -33,58 +28,45 @@ jobs:
           echo "changed_files=$files" >> $GITHUB_ENV
           echo "Changed Markdown files: $files"
 
-      # Step 4: Skip workflow if no Markdown files changed
       - name: Skip if no Markdown files changed
         if: ${{ env.changed_files == '' }}
         run: |
           echo "No Markdown files changed in this PR. Skipping link check."
           exit 0
 
-      # Step 5: Dump base branch links for changed files only (no fragments)
-      - name: Dump base branch links
-        uses: lycheeverse/lychee-action@v2
-        with:
-          args: "--dump ${{ env.changed_files }}"
-          output: ./existing-links.txt
-        continue-on-error: true
-
-      # Step 6: Stash untracked files and switch back to feature branch
-      - name: Stash untracked files
-        run: git stash push --include-untracked
-
-      - name: Check out feature branch
-        run: git checkout ${{ github.head_ref }}
-
-      - name: Apply stashed changes
-        run: git stash pop || true
-
-      # Step 7: Add base branch links to .lycheeignore
-      - name: Update ignore file
-        run: |
-          if [ -f "existing-links.txt" ]; then
-            cat existing-links.txt >> .lycheeignore
-          fi
-
-      # Step 8: Run Lychee on changed files one by one
+      # Run Lychee and save full report
       - name: Run Lychee link checker
+        id: lychee
+        continue-on-error: true
         run: |
           for f in ${{ env.changed_files }}; do
             echo "Checking links in $f"
-            lychee --no-progress --include-fragments "$f"
+            lychee --no-progress "$f" >> lychee-full-report.txt || true
           done
 
-      # Step 9: Provide a helpful failure message
-      - name: Helpful failure message
-        if: ${{ failure() }}
+          # Extract only broken links for PR comment
+          grep -E "❌|ERROR" lychee-full-report.txt > lychee-broken.txt || true
+
+          echo "full_report=lychee-full-report.txt" >> $GITHUB_ENV
+          echo "broken_report=lychee-broken.txt" >> $GITHUB_ENV
+
+      # Upload artifacts for debugging (optional)
+      - name: Upload full Lychee report
+        uses: actions/upload-artifact@v4
+        with:
+          name: lychee-full-report
+          path: ${{ env.full_report }}
+
+      # Post PR comment with only broken links
+      - name: Comment broken links on PR
+        if: always() # Always run so even if broken, we post
+        uses: marocchino/sticky-pull-request-comment@v2
+        with:
+          path: ${{ env.broken_report }}
+
+      # Fail if broken links exist
+      - name: Fail on broken links
+        if: steps.lychee.outcome == 'failure'
         run: |
-          echo "::error::Link check failed! Please review the broken links reported above."
-          echo ""
-          echo "If certain links are valid but fail due to:"
-          echo "- CAPTCHA challenges"
-          echo "- IP blocking"
-          echo "- Authentication requirements"
-          echo "- Rate limiting"
-          echo ""
-          echo "Consider adding them to .lycheeignore to bypass future checks."
-          echo "Format: Add one URL pattern per line"
+          echo "::error::Broken links detected. See the PR comment above for details."
           exit 1

From 7886d9f864a92f2dbbe4a1f06831be32c6469f40 Mon Sep 17 00:00:00 2001
From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com>
Date: Fri, 29 Aug 2025 15:04:56 +0200
Subject: [PATCH 13/15] Updated check-pr-links.yml

---
 .github/workflows/check-pr-links.yml | 65 +++++++++++++++++++++-------
 1 file changed, 49 insertions(+), 16 deletions(-)

diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index ad4354b32ee..80c117e1b5e 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -5,66 +5,99 @@ on:
     branches:
       - main
     paths:
-      - '**/*.md'
+      - '**/*.md'   # Only trigger workflow if any Markdown files change
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
     steps:
+      # Step 1: Checkout the repository
       - name: Checkout GitHub repo
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
-      # Get changed Markdown files
+      # Step 2: Check out base branch
+      - name: Check out base branch
+        run: git checkout ${{ github.event.pull_request.base.ref }}
+
+      # Step 3: Get changed Markdown files
       - name: Get changed Markdown files
         id: changed-files
         run: |
-          mapfile -t files_array < <(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
-          files=""
-          for f in "${files_array[@]}"; do
-            files="$files \"$f\""
-          done
+          files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.head_ref }} | grep '\.md$' || true)
           echo "changed_files=$files" >> $GITHUB_ENV
-          echo "Changed Markdown files: $files"
+          echo "Changed Markdown files:"
+          echo "$files"
 
+      # Step 4: Skip workflow if no Markdown files changed
       - name: Skip if no Markdown files changed
         if: ${{ env.changed_files == '' }}
         run: |
           echo "No Markdown files changed in this PR. Skipping link check."
           exit 0
 
-      # Run Lychee and save full report
+      # Step 5: Dump base branch links for changed files only (no fragments)
+      - name: Dump base branch links
+        uses: lycheeverse/lychee-action@v2
+        with:
+          args: "--dump ${{ env.changed_files }}"
+          output: ./existing-links.txt
+        continue-on-error: true
+
+      # Step 6: Stash untracked files and switch back to feature branch
+      - name: Stash untracked files
+        run: git stash push --include-untracked
+
+      - name: Check out feature branch
+        run: git checkout ${{ github.head_ref }}
+
+      - name: Apply stashed changes
+        run: git stash pop || true
+
+      # Step 7: Add base branch links to .lycheeignore
+      - name: Update ignore file
+        run: |
+          if [ -f "existing-links.txt" ]; then
+            cat existing-links.txt >> .lycheeignore
+          fi
+
+      # Step 8: Run Lychee on changed files one by one and extract broken links
       - name: Run Lychee link checker
         id: lychee
         continue-on-error: true
         run: |
+          rm -f lychee-full-report.txt
           for f in ${{ env.changed_files }}; do
             echo "Checking links in $f"
-            lychee --no-progress "$f" >> lychee-full-report.txt || true
+            lychee --no-progress --include-fragments "$f" >> lychee-full-report.txt || true
           done
 
-          # Extract only broken links for PR comment
-          grep -E "❌|ERROR" lychee-full-report.txt > lychee-broken.txt || true
+          # Extract only broken links
+          if [ -f lychee-full-report.txt ]; then
+            grep -E "❌|ERROR" lychee-full-report.txt > lychee-broken.txt || true
+          else
+            echo "No report generated." > lychee-broken.txt
+          fi
 
           echo "full_report=lychee-full-report.txt" >> $GITHUB_ENV
           echo "broken_report=lychee-broken.txt" >> $GITHUB_ENV
 
-      # Upload artifacts for debugging (optional)
+      # Step 9: Upload full report for debugging (optional)
       - name: Upload full Lychee report
         uses: actions/upload-artifact@v4
         with:
           name: lychee-full-report
           path: ${{ env.full_report }}
 
-      # Post PR comment with only broken links
+      # Step 10: Post PR comment with only broken links
       - name: Comment broken links on PR
-        if: always() # Always run so even if broken, we post
+        if: always()
         uses: marocchino/sticky-pull-request-comment@v2
         with:
           path: ${{ env.broken_report }}
 
-      # Fail if broken links exist
+      # Step 11: Fail if broken links exist
       - name: Fail on broken links
         if: steps.lychee.outcome == 'failure'
         run: |

From b58c4c9577d203081018d7b3711179fdea97d5c0 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Mon, 1 Sep 2025 09:47:31 +0200
Subject: [PATCH 14/15] updated config files

---
 .github/lychee.toml                  |  5 +-
 .github/workflows/check-pr-links.yml | 76 ++++++----------------------
 2 files changed, 16 insertions(+), 65 deletions(-)

diff --git a/.github/lychee.toml b/.github/lychee.toml
index 37c5817e35f..976d16ee418 100644
--- a/.github/lychee.toml
+++ b/.github/lychee.toml
@@ -11,10 +11,7 @@ no_progress = false
 ############################# Cache ###############################
 # Enable link caching. This can be helpful to avoid checking the same links on
 # multiple runs.
-cache = true
-
-# Discard all cached requests older than this duration.
-max_cache_age = "1d"
+cache = false
 
 ############################# Runtime #############################
 # Maximum number of concurrent link checks.
diff --git a/.github/workflows/check-pr-links.yml b/.github/workflows/check-pr-links.yml
index 80c117e1b5e..ce7d91a6ff1 100644
--- a/.github/workflows/check-pr-links.yml
+++ b/.github/workflows/check-pr-links.yml
@@ -5,23 +5,18 @@ on:
     branches:
       - main
     paths:
-      - '**/*.md'   # Only trigger workflow if any Markdown files change
+      - '**/*.md'
 
 jobs:
   check-links:
     runs-on: ubuntu-latest
     steps:
-      # Step 1: Checkout the repository
-      - name: Checkout GitHub repo
-        uses: actions/checkout@v4
+      # Checkout repo
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0
 
-      # Step 2: Check out base branch
-      - name: Check out base branch
-        run: git checkout ${{ github.event.pull_request.base.ref }}
-
-      # Step 3: Get changed Markdown files
+      # Find changed Markdown files
       - name: Get changed Markdown files
         id: changed-files
         run: |
@@ -30,39 +25,14 @@ jobs:
           echo "Changed Markdown files:"
           echo "$files"
 
-      # Step 4: Skip workflow if no Markdown files changed
+      # Skip if no Markdown files
       - name: Skip if no Markdown files changed
         if: ${{ env.changed_files == '' }}
         run: |
-          echo "No Markdown files changed in this PR. Skipping link check."
+          echo "No Markdown files changed. Skipping link check."
           exit 0
 
-      # Step 5: Dump base branch links for changed files only (no fragments)
-      - name: Dump base branch links
-        uses: lycheeverse/lychee-action@v2
-        with:
-          args: "--dump ${{ env.changed_files }}"
-          output: ./existing-links.txt
-        continue-on-error: true
-
-      # Step 6: Stash untracked files and switch back to feature branch
-      - name: Stash untracked files
-        run: git stash push --include-untracked
-
-      - name: Check out feature branch
-        run: git checkout ${{ github.head_ref }}
-
-      - name: Apply stashed changes
-        run: git stash pop || true
-
-      # Step 7: Add base branch links to .lycheeignore
-      - name: Update ignore file
-        run: |
-          if [ -f "existing-links.txt" ]; then
-            cat existing-links.txt >> .lycheeignore
-          fi
-
-      # Step 8: Run Lychee on changed files one by one and extract broken links
+      # Run Lychee
       - name: Run Lychee link checker
         id: lychee
         continue-on-error: true
@@ -73,33 +43,17 @@ jobs:
             lychee --no-progress --include-fragments "$f" >> lychee-full-report.txt || true
           done
 
-          # Extract only broken links
-          if [ -f lychee-full-report.txt ]; then
-            grep -E "❌|ERROR" lychee-full-report.txt > lychee-broken.txt || true
+          # Extract only broken links for log
+          if grep -qE "❌|ERROR" lychee-full-report.txt; then
+            echo "❌ Broken links found:"
+            grep -E "❌|ERROR" lychee-full-report.txt
           else
-            echo "No report generated." > lychee-broken.txt
+            echo "✅ No broken links found."
           fi
 
-          echo "full_report=lychee-full-report.txt" >> $GITHUB_ENV
-          echo "broken_report=lychee-broken.txt" >> $GITHUB_ENV
-
-      # Step 9: Upload full report for debugging (optional)
-      - name: Upload full Lychee report
+      # Upload full report as artifact
+      - name: Upload Lychee full report
         uses: actions/upload-artifact@v4
         with:
           name: lychee-full-report
-          path: ${{ env.full_report }}
-
-      # Step 10: Post PR comment with only broken links
-      - name: Comment broken links on PR
-        if: always()
-        uses: marocchino/sticky-pull-request-comment@v2
-        with:
-          path: ${{ env.broken_report }}
-
-      # Step 11: Fail if broken links exist
-      - name: Fail on broken links
-        if: steps.lychee.outcome == 'failure'
-        run: |
-          echo "::error::Broken links detected. See the PR comment above for details."
-          exit 1
+          path: lychee-full-report.txt

From 122a82a7ca04efb882a853724a5d5e02e401a7c5 Mon Sep 17 00:00:00 2001
From: Esha Noronha 
Date: Mon, 1 Sep 2025 09:50:35 +0200
Subject: [PATCH 15/15] testing lychee

---
 .../upgrade-from-8-to-latest.md               | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
index 49da82b82c6..52f91e0d0d9 100644
--- a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
+++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md
@@ -108,3 +108,27 @@ This concludes this tutorial. Find related information and further reading in th
 * [Issue tracker for known issues with Content Migration](https://github.com/umbraco/UmbracoDocs/issues)
 * [Configuration in modern Umbraco](../../../../reference/configuration/)
 * [Configuration in legacy Umbraco](https://our.umbraco.com/documentation/Reference/Configuration-for-Umbraco-7-and-8/)
+
+## Lychee Test Links 
+
+### ✅ Valid links 
+
+- [Google](https://www.google.com) 
+- [GitHub](https://github.com) 
+- [Upgrading Deploy](https://docs.umbraco.com/umbraco-deploy/upgrading/upgrades) 
+
+### ❌ Broken links 
+
+- [Broken domain](https://thisdomaindoesnotexist.openai) 
+- [404 page](https://docs.umbraco.com/umbraco-forms/editor/attaching-workflows/workflows-types) 
+- [Typo in domain](https://www.googlle.com) 
+- [Commerce Configuration](https://docs.umbraco.com/umbraco-commerce/getting-started/umbraco-configurations) 
+
+### ❌ Wrong anchors 
+
+- [Wrong anchor](https://docs.umbraco.com/umbraco-forms/editor/attaching-workflows#non-existing-anchor) 
+
+### ⚠️ Timeout-prone links (optional tests) 
+
+- [Umbraco Issues](https://issues.umbraco.org/issue/U4-1234) 
+- [Example CAPTCHA site](https://www.linkedin.com)
\ No newline at end of file