Skip to content

Conversation

@ihabadham
Copy link
Collaborator

@ihabadham ihabadham commented Dec 12, 2025

Summary

Replaces the broken markdown-link-check setup with lychee for link checking.

Problem

The previous setup using Wandalen/wretry.action wrapper with tcort/markdown-link-check was silently failing due to a Node.js 21 ESM compatibility bug (see #2217). The tool crashed on every file, but the crash message didn't match the error detection pattern, so CI falsely reported "All links are good!" - while actually checking zero links.

Solution

Migrate to lychee, a Rust-based link checker that:

  • Built-in retry for ALL transient errors (502, 503, 504) - what PR Add retry logic for transient HTTP errors in markdown link checker #1899 wanted but couldn't achieve
  • Proper exit codes (2 = link failures, not relying on string matching)
  • Faster execution (~12 seconds vs 4+ minutes for 550 links)
  • Result caching for faster repeated runs
  • No Docker/Node.js compatibility issues

Changes

  • Replace .github/markdown-link-check-config.json with .lychee.toml
  • Update workflow to use lycheeverse/lychee-action@v2
  • Add .lycheecache to .gitignore

Test Plan

  • CI link checker workflow runs successfully
  • lychee correctly identifies broken links (will show ~26 errors from known broken URLs that need separate fixes)

Note

This PR will fail CI because it correctly detects ~26 broken links that the previous setup missed. Those links need to be fixed in a separate PR (like #2216).

Fixes #2217

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Migrated CI markdown link checking to Lychee with caching and workflow updates.
    • Removed the old markdown link-check configuration and added a new Lychee configuration.
    • Updated ignore rules to stop tracking Lychee cache artifacts.
  • Documentation

    • Rewrote and updated release notes and migration docs with revised links and formatting.
    • Edited contributing guidance, changelog link targets, and project documentation entries.

✏️ Tip: You can customize this high-level summary in your review settings.

The previous setup using Wandalen/wretry.action wrapper with tcort/markdown-link-check
was silently failing due to a Node.js 21 ESM compatibility bug. The markdown-link-check
tool crashed on every file, but the crash message didn't match the error detection
pattern, so CI falsely reported "All links are good!"

This migration to lychee provides:
- Built-in retry for ALL transient errors (502, 503, 504), not just 429
- Proper exit codes (2 = link failures)
- Faster execution (Rust-based, async)
- Result caching for faster repeated runs
- No Docker/Node.js compatibility issues

Fixes #2217

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 12, 2025

Walkthrough

Replaces the markdown-link-check pipeline with Lychee: deletes .github/markdown-link-check-config.json, adds .lychee.toml, updates .github/workflows/check-markdown-links.yml to use lycheeverse/lychee-action@v2 with caching, adds .lycheecache to .gitignore, and updates several Pro docs links and docs text. (50 words)

Changes

Cohort / File(s) Summary
Workflow update
\.github/workflows/check-markdown-links.yml
Replaces Wandalen/wretry.action + markdown-link-check step with actions/cache@v4 + lycheeverse/lychee-action@v2, switching config reference from .github/markdown-link-check-config.json to .lychee.toml, adding cache restore/save steps and lychee args.
Config migration
Removed: \.github/markdown-link-check-config.json
Added: \.lychee.toml
Removes old markdown-link-check JSON config and adds Lychee config with accept codes, timeout, retry settings, excludes/exclude_path, include_fragments=false, and cache settings (max_cache_age, cache enabled).
Repository housekeeping
\.gitignore
Adds .lycheecache to the ignore list.
Docs & release notes
Multiple: react_on_rails_pro/..., react_on_rails_pro/CHANGELOG.md, react_on_rails_pro/CONTRIBUTING.md, react_on_rails_pro/docs/release-notes/..., PROJECTS.md
Non-functional documentation edits: update RSC tutorial links to relative paths, reword/reformat release notes and contributing docs, adjust PROJECTS.md entries and minor link/format fixes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant GH as GitHub Actions
    participant Cache as actions/cache@v4
    participant Lychee as lycheeverse/lychee-action@v2
    participant Repo as Repository (.lychee.toml + docs)

    GH->>Cache: restore `.lycheecache` (key: lychee-...)
    GH->>Lychee: run lychee with `--config .lychee.toml` on targets (docs/, **/*.md)
    Lychee->>Repo: read `.lychee.toml` (accept, timeout, retries, excludes, cache)
    Lychee->>Repo: scan markdown files and request external URLs
    Lychee-->>GH: return results (pass/fail)
    GH->>Cache: save updated `.lycheecache`
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify .lychee.toml reproduces required ignore rules and retry/timeout semantics from the removed JSON config.
  • Confirm workflow cache key/path and restore/save steps are correct and safe for CI concurrency.
  • Validate lychee action args and scan targets to ensure intended files are covered and no unintended exclusions.

Possibly related PRs

Suggested reviewers

  • justin808
  • AbanoubGhadban
  • Judahmeek
  • alexeyr-ci2

Poem

🐇 I hopped through README trails tonight,
Found an old JSON out of sight.
Planted Lychee, cached my paw,
Scanned each link, then fixed the flaw.
Nibble, patch, and bound — delight! ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Migrate from markdown-link-check to lychee' directly and clearly summarizes the main change—replacing the markdown link checker tool with lychee.
Linked Issues check ✅ Passed The PR successfully addresses the coding requirements from issue #2217 by implementing a migration to lychee, configuring proper retries, expanding check scope, fixing broken documentation links, and excluding problematic URLs.
Out of Scope Changes check ✅ Passed Changes to react_on_rails_pro documentation files (CHANGELOG.md, CONTRIBUTING.md, release notes, and PROJECTS.md) appear tangential to the core lychee migration but are justified as fixing broken links discovered during the migration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch migrate-to-lychee-link-checker

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e00fba and 818b535.

📒 Files selected for processing (1)
  • .lychee.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .lychee.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review
  • GitHub Check: markdown-link-check

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Dec 13, 2025

Code Review: Migrate from markdown-link-check to lychee

Overall Assessment

Excellent PR - This is a well-researched, necessary fix that addresses a critical CI failure. The migration from markdown-link-check to lychee is well-justified and properly implemented.


Strengths

  1. Clear Problem Statement: The PR description clearly explains why the migration is necessary (Node.js 21 ESM compatibility bug causing silent failures).

  2. Performance Improvement: ~12 seconds vs 4+ minutes for 550 links is a significant improvement.

  3. Better Reliability: Built-in retry for transient errors (502/503/504) without needing wrapper actions.

  4. Proper Exit Codes: Uses proper exit codes instead of error pattern matching, making failures deterministic.

  5. Configuration Migration: Properly migrated all relevant exclusion patterns from the old config to .lychee.toml.

  6. Caching: Implements result caching to speed up repeated runs.


Configuration Review

Good Choices

  • Accept codes: matches previous behavior (200, 206)
  • Retry settings: max_retries = 3, retry_wait_time = 10 matches previous config
  • Timeout: 20 seconds matches previous behavior
  • Fragment checking disabled: Good call, fragments are notoriously flaky

Potential Improvements

  1. Exclude patterns: You've added several new exclusions (medium.com, blog.shakacode.com, reactrails.com, etc.) that weren't in the original config. Consider documenting why each was added.

  2. Cache key: The cache key includes github.sha, meaning each commit gets a new cache entry. Consider using a branch-based key instead for better cache reuse across commits.

  3. Missing replacement pattern: The old config had a replacement pattern for relative links starting with /. Does lychee handle this automatically, or do you need to add --base-url?


Testing Concerns

Expected CI Failure

The PR description states this will fail CI because it correctly detects ~26 broken links.

Recommendation: Before merging, you should either:

  1. Fix the broken links first in a separate PR (like Fix outdated paths after monorepo restructuring #2216), then merge this
  2. Merge this with expected failures, then immediately fix the broken links
  3. Temporarily add the known-broken URLs to exclude patterns with a TODO comment

Option 1 is cleanest from a CI health perspective (aligns with CLAUDE.md's "Master Branch Health Monitoring" guidelines).

Verification Steps Needed

According to CLAUDE.md, you should:

  1. Test locally with lychee to verify behavior
  2. Verify it catches the known broken links from Link checker CI passes despite broken links on master #2217
  3. Check that excluded URLs are properly skipped

Security & Best Practices

Security

  • No credentials or sensitive data exposed ✅
  • Using a well-maintained action ✅
  • No arbitrary code execution ✅

Pin Action Version

Consider pinning the action to a specific version instead of @v2 for better security and reproducibility.


Performance Considerations

  • 40x faster execution (4 min → 12 sec) ✅
  • Caching reduces redundant checks ✅
  • Rust-based tool is more efficient than Node.js ✅

Optimization Ideas:

  1. Lychee supports --max-concurrency - consider adding if you want to tune performance
  2. For large repos, you could check only modified files on PRs (though the speed may make this unnecessary)

Alignment with CLAUDE.md Guidelines

Post-Merge Requirements

After merging:

  1. Monitor CI on master immediately (within 30 minutes)
  2. Don't leave master broken - if the expected ~26 link failures cause issues, fix them quickly
  3. Verify the workflow actually works - confirm it's checking links and not silently failing like the previous setup

Final Recommendations

Before Merging:

  1. Test locally with lychee to verify behavior
  2. Decide how to handle the 26 broken links (fix first, or merge with failures)
  3. Consider cache key optimization (use branch-based key instead of SHA)
  4. Verify relative link handling (replacement pattern migration)
  5. Pin action version for reproducibility

After Merging:

  1. Monitor CI immediately (CLAUDE.md requirement)
  2. Fix broken links within the hour if not already fixed
  3. Update any contributor docs that reference link checking

Verdict

LGTM with minor suggestions 👍

This is a necessary and well-executed migration. The suggestions above are optimizations and safeguards, not blockers. The core change is solid.

Estimated risk: Low - Switching to a more reliable tool with proper testing
Maintenance burden: Lower than previous setup (no wrapper needed, faster execution)

Great work diagnosing and fixing this!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/check-markdown-links.yml (1)

3-15: paths: '**.md' but lychee only scans docs/ — confirm intended coverage. If you want repo-wide markdown link checking, adjust args (or narrow the workflow paths to docs/**.md).

-          args: --config .lychee.toml docs/
+          args: --config .lychee.toml .
🧹 Nitpick comments (1)
.github/workflows/check-markdown-links.yml (1)

26-37: Cache key is effectively “no cache” across commits; use a stable key. ${{ github.sha }} guarantees a new primary key every commit, reducing hit rate and increasing cache churn. Prefer OS + config hash (and optionally a docs hash) so caches actually get reused.

       - name: Restore lychee cache
         uses: actions/cache@v4
         with:
           path: .lycheecache
-          key: cache-lychee-${{ github.sha }}
-          restore-keys: cache-lychee-
+          key: lychee-${{ runner.os }}-${{ hashFiles('.lychee.toml') }}
+          restore-keys: |
+            lychee-${{ runner.os }}-

Also, per workflow guidelines, please ensure you’ve tested the updated workflow locally (e.g., with act or an equivalent) before iterating via CI.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8227421 and 02d5aa7.

📒 Files selected for processing (4)
  • .github/markdown-link-check-config.json (0 hunks)
  • .github/workflows/check-markdown-links.yml (2 hunks)
  • .gitignore (1 hunks)
  • .lychee.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • .github/markdown-link-check-config.json
🧰 Additional context used
📓 Path-based instructions (1)
**/.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

Changes to CI workflows require comprehensive local testing BEFORE pushing to avoid CI failure cycles

Files:

  • .github/workflows/check-markdown-links.yml
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for open-source features, bug fixes, breaking changes, deprecations, and performance improvements using format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)`
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to **/.github/workflows/*.yml : Changes to CI workflows require comprehensive local testing BEFORE pushing to avoid CI failure cycles

Applied to files:

  • .github/workflows/check-markdown-links.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22, latest)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: precompile-check
  • GitHub Check: build
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review
🔇 Additional comments (2)
.gitignore (1)

92-94: .lycheecache ignore rule looks correct.

.lychee.toml (1)

4-49: No issues identified. All configuration keys and types in .lychee.toml (accept, max_retries, retry_wait_time, timeout, exclude, exclude_path, include_fragments, cache, max_cache_age) are valid for lychee v0.21.0 (the default version used by lycheeverse/lychee-action@v2). The schema is correct and CI will not silently ignore these settings.

ihabadham and others added 2 commits December 13, 2025 02:06
Removed exclusions that were incorrectly added:
- blog.shakacode.com - returns 200 (redirects to Medium)
- reactrails.com - returns 200
- react-webpack-rails-tutorial - not archived, broken links are real

These sites work fine and the tutorial repo links are genuinely broken
(the specific files no longer exist at those paths).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
react.dev returns 308 for some URLs - this is a valid permanent
redirect, not a broken link. Added 308 to accept list alongside
200 and 206.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Dec 13, 2025

Code Review - PR #2219: Migrate from markdown-link-check to lychee

✅ Overall Assessment

Excellent migration! This PR solves a critical CI reliability issue with a well-researched, properly configured replacement. The solution is cleaner, faster, and more robust than the previous setup.


🎯 Strengths

1. Problem Clearly Identified

2. Configuration Migration is Accurate

✅ All exclusion patterns properly migrated from .github/markdown-link-check-config.json to .lychee.toml
✅ HTTP status codes preserved (accept = ["200", "206"])
✅ Retry logic maintained (max_retries = 3)
✅ Timeout settings preserved (timeout = 20)

3. Significant Performance Improvement

  • 12 seconds vs 4+ minutes for 550 links
  • Built-in caching with actions/cache@v4
  • Rust-based tool = no Node.js/Docker compatibility issues

4. Proper CI Integration

✅ Cache key strategy is correct with fallback
fail: true ensures broken links fail CI
✅ Path triggers updated to reference .lychee.toml


🔍 Minor Observations & Suggestions

1. Configuration: Consider Adding base Parameter

The old config had this path replacement:

"replacementPatterns": [{
  "pattern": "^/",
  "replacement": "https://github.com/shakacode/react_on_rails/blob/master/"
}]

In lychee, you can achieve this with:

# Base URL for relative links starting with /
base = "https://github.com/shakacode/react_on_rails/blob/master"

However, this may not be needed if all your markdown files use proper relative links. Worth checking if there are any /docs/... style links in the codebase.

2. Excluded Patterns: Additional Consideration

You excluded ^https://medium\.com but the old config did not have this. Is this based on observed issues during testing? If so, great - just want to confirm it is intentional (Medium can be flaky with bot detection).

3. Fragment Checking Disabled

include_fragments = false

This is reasonable for stability, but worth noting that broken anchor links will not be detected. For example:

  • [Link](#non-existent-section) will not fail
  • Cross-document anchors like /other-doc.md#section will not be validated

This is a trade-off between stability and thoroughness - I think you made the right call for this project's needs.

4. Cache Age Strategy

max_cache_age = "1d"

One day is reasonable, but consider:

  • Weekly scheduled runs (Monday 8am) might want fresh checks
  • Could add cache refresh option for manual runs

Not a blocker - just something to monitor.


🛡️ Security & Best Practices

No security concerns - lychee is a well-maintained, widely-used tool
Pinned action version - consider pinning to SHA for extra security in the future
No credentials exposed - all configuration is in plain sight
Proper gitignore for .lycheecache


🧪 Test Coverage

What's Well Covered:

✅ CI workflow will run on this PR and demonstrate lychee works
✅ Known broken links will properly fail (as documented in PR description)

Potential Gap:

The workflow only checks docs/ directory:

args: --config .lychee.toml docs/

Question: Should it also check:

  • Root-level .md files (README.md, CONTRIBUTING.md, CHANGELOG.md, etc.)?
  • react_on_rails_pro/ directory docs if they exist?

If root-level files are intentionally excluded, that's fine - just want to confirm.


📊 Performance Considerations

Massive improvement: 4+ minutes → 12 seconds
Caching implemented correctly
Retry logic handles transient failures without external wrapper

Recommendation: Monitor first few runs to ensure cache strategy is effective. You should see:

  • First run: ~12 seconds (cold cache)
  • Subsequent runs with unchanged URLs: ~2-5 seconds (warm cache)

📝 Documentation & Changelog

Should Update CHANGELOG.md?

Based on CLAUDE.md guidelines:

Update CHANGELOG.md for user-visible changes only (features, bug fixes, breaking changes...)
Do NOT add entries for: linting, formatting, refactoring, tests, or documentation fixes

This is technically a CI/tooling improvement, not a user-facing change. However, it fixes a critical bug (CI was silently failing). I'd recommend a brief entry:

#### Bug Fixes
- [PR 2219](https://github.com/shakacode/react_on_rails/pull/2219) by [ihabadham](https://github.com/ihabadham): Fixed broken markdown link checker CI workflow by migrating to lychee

But this is borderline - defer to maintainer preference.


✅ Final Verdict

APPROVE

This is a high-quality PR that:

  1. Solves a critical, well-documented problem
  2. Uses a superior tool with better characteristics
  3. Properly migrates all configuration
  4. Improves performance dramatically
  5. Has clear documentation of trade-offs

The CI failures are expected and correct (as documented in the PR description) - they represent actual broken links that need fixing in a follow-up PR.


🚀 Recommended Next Steps

  1. Merge this PR (after confirming scope of docs/ vs root .md files)
  2. Fix the ~26 broken links in a follow-up PR (like Fix outdated paths after monorepo restructuring #2216)
  3. Monitor caching performance in first few CI runs
  4. Consider adding lychee to pre-commit hooks for local validation (optional)

Great work! This is exactly the kind of thorough, evidence-based infrastructure improvement the project needs.

🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Dec 13, 2025

Code Review: Migration to Lychee Link Checker

I've reviewed this PR and it looks excellent overall! This is a well-executed migration that addresses a critical CI failure. Here's my detailed feedback:


Strengths

  1. Problem Identification: Correctly identified that markdown-link-check was silently failing due to Node.js 21 ESM compatibility issues
  2. Tool Selection: Lychee is a solid choice - Rust-based, fast, reliable, with built-in retry logic
  3. Configuration Migration: All exclusion patterns from the old config were properly migrated
  4. Performance Improvement: ~12s vs 4+ minutes is a massive win
  5. Caching Strategy: GitHub Actions cache integration is properly configured
  6. Clear Documentation: PR description clearly explains what will break and why

🔍 Potential Issues

1. Limited Scope - Only Checking docs/ Directory

Location: .github/workflows/check-markdown-links.yml:36

args: --config .lychee.toml docs/

Issue: The workflow only checks docs/, but there are many important markdown files at the root and in other directories:

  • README.md, CHANGELOG.md, CONTRIBUTING.md, CLAUDE.md (root level)
  • .github/*.md (PR templates, issue templates)
  • analysis/*.md (analysis documents)
  • react_on_rails/ and react_on_rails_pro/ subdirectories

Recommendation: Check all markdown files in the repository:

args: --config .lychee.toml '**/*.md'

Or explicitly list all directories:

args: --config .lychee.toml docs/ *.md .github/*.md analysis/ react_on_rails/ react_on_rails_pro/

Note: The old configuration used folder-path: docs/, so this maintains backwards compatibility. However, since you're doing a migration anyway, this is a good opportunity to expand coverage.


2. Missing HTTP Status Codes in accept

Location: .lychee.toml:6

accept = ["200", "206", "308"]

Issue: Several important HTTP status codes are missing:

  • 301 - Permanent Redirect (very common for updated URLs)
  • 302 - Temporary Redirect (also common)
  • 429 - Too Many Requests (should be accepted with retries, not treated as failure)

The old config had:

"aliveStatusCodes": [200, 206]
"retryOn429": true

Recommendation:

# Accept successful responses and redirects
# 200 = OK, 206 = Partial Content
# 301 = Permanent Redirect, 302 = Temporary Redirect, 308 = Permanent Redirect
# 429 = Too Many Requests (with retry)
accept = ["200", "206", "301", "302", "308", "429"]

3. Retry Configuration May Be Insufficient

Location: .lychee.toml:9-10

max_retries = 3
retry_wait_time = 10

Comparison: The old config had:

"retryCount": 5,
"fallbackRetryDelay": "30s"

Issue: The new config is more aggressive (less retries, shorter delays). This might cause flaky failures on slow/rate-limited sites.

Recommendation: Match or exceed the old config's resilience:

max_retries = 5
retry_wait_time = 30  # seconds between retries

4. Missing medium.com Exclusion Context

Location: .lychee.toml:24

'^https://medium\.com',

Issue: This pattern was added to the new config but wasn't in the old config. While this is likely correct (Medium does block bots), it should be documented in the PR or in a comment.

Recommendation: Add a comment explaining why it was added:

# Sites that block bots or require auth
'^https://www\.npmjs\.(org|com)',
'^https://medium\.com',  # Added: Medium blocks automated checkers
'^https://www\.linkedin\.com',

5. GitHub Blob URLs Pattern Not Migrated

Location: .lychee.toml (missing)

Issue: The old config had special handling for GitHub blob URLs:

"replacementPatterns": [{
  "pattern": "^/",
  "replacement": "https://github.com/shakacode/react_on_rails/blob/master/"
}]

This converted relative paths like /docs/foo.md to full GitHub URLs. Lychee has a base option for this.

Impact: If any markdown files use relative GitHub links starting with /, they won't be checked correctly.

Recommendation: Add to config:

# Base URL for relative links starting with /
base = "https://github.com/shakacode/react_on_rails/blob/master"

Or verify that no markdown files use this pattern:

grep -r '](/' docs/ --include='*.md' | grep -v 'localhost' | head -10

6. HTTP Headers Not Migrated

Location: .lychee.toml (missing)

Issue: The old config set custom headers for GitHub:

"httpHeaders": [{
  "urls": ["https://docs.github.com", "https://github.com"],
  "headers": { "Accept": "text/html" }
}]

Lychee supports this via [[headers]] sections.

Impact: GitHub API might return JSON instead of HTML, potentially causing link check failures.

Recommendation: Add headers config:

# Custom headers for specific domains
[[headers]]
pattern = "https://github\.com"
"Accept" = "text/html"

[[headers]]
pattern = "https://docs\.github\.com"
"Accept" = "text/html"

🔒 Security Considerations

Cache Security: The .lycheecache is properly added to .gitignore to prevent accidental commits of potentially sensitive URLs.

No Secrets Exposed: Configuration doesn't expose any sensitive information.


🧪 Test Coverage

Missing: The PR description mentions "This PR will fail CI because it correctly detects ~26 broken links." However:

  1. Recommendation: Add a step in the workflow to generate a report of broken links as a GitHub artifact, so they can be reviewed without failing CI during the transition:
- name: Check markdown links
  uses: lycheeverse/lychee-action@v2
  with:
    args: --config .lychee.toml docs/
    fail: false  # Don't fail during migration
  
- name: Upload link check results
  uses: actions/upload-artifact@v4
  with:
    name: link-check-results
    path: lychee/out.md

Then create a follow-up PR to fix the broken links and set fail: true.

  1. Alternative: Keep fail: true but document in the PR that this is expected to fail and link to issue Fix outdated paths after monorepo restructuring #2216.

📋 Action Items

Must Fix (before merge):

  1. ✅ Verify scope - decide if you want to check only docs/ or expand to all .md files
  2. ✅ Add missing HTTP status codes (301, 302, 429) to accept
  3. ✅ Consider increasing retry resilience to match old config (5 retries, 30s delay)

Should Fix:
4. ✅ Add GitHub headers configuration
5. ✅ Add base URL or verify no relative / links exist
6. ✅ Document why medium.com was added

Nice to Have:
7. ✅ Consider artifact upload strategy during migration to avoid blocking the PR on known broken links


📝 Documentation

The PR description is excellent and clearly explains:

  • The problem
  • The solution
  • What will break
  • Why it's better

Suggestion: Add a note in CLAUDE.md or contributor docs about the new link checker tool, so future contributors know:

  • How to run it locally: lychee --config .lychee.toml docs/
  • How to update exclusions
  • Link to lychee documentation

🎯 Overall Assessment

This is a high-quality PR that solves a real problem. The migration is well-executed, but there are some configuration gaps that could cause issues:

  • Scope limitation (only checking docs/)
  • Missing redirect status codes (301, 302)
  • Missing GitHub headers

I'd recommend addressing the "Must Fix" items before merging, as they're likely to cause issues with external links that redirect or require specific headers.

Great work on identifying and solving this silent failure! 🎉


References:

@ihabadham ihabadham requested review from alexeyr-ci2 and justin808 and removed request for justin808 December 13, 2025 00:12
.lychee.toml Outdated

# Accept successful responses and permanent redirects
# 200 = OK, 206 = Partial Content, 308 = Permanent Redirect
accept = ["200", "206", "308"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is 206 OK?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It was in the old config. Not sure why I added it then but we don't actually need it. Removed

@justin808
Copy link
Member

@ihabadham can you have CC post a reply here if you want to do the suggestions in #2219 (comment) in this PR or make a follow up or not do it.

@claude
Copy link

claude bot commented Dec 13, 2025

Code Review: Migrate from markdown-link-check to lychee

✅ Overall Assessment

This is a well-executed migration that solves a critical problem with the existing link checker. The PR is well-documented, addresses a real issue (#2217), and uses a modern, reliable tool.


🎯 Strengths

  1. Excellent Problem Documentation

    • Clear explanation of the silent failure issue
    • Performance comparison (12s vs 4+ minutes)
    • Honest about expected CI failures
  2. Well-Structured Configuration

    • Proper migration of all exclusion patterns from old config
    • Sensible defaults (20s timeout, 3 retries)
    • Cache configuration for faster repeated runs
    • Good comments explaining each section
  3. Workflow Improvements

    • Built-in retry mechanism (no need for wretry.action wrapper)
    • Proper exit codes (no string matching hacks)
    • Cache restoration step for performance

🔍 Suggestions & Questions

1. Accept Status Codes

Why exclude 301 (Permanent Redirect)? The old config had aliveStatusCodes: [200, 206]. You added 308, but consider if 301 should also be accepted. Many sites use 301 for permanent redirects.

2. Retry Configuration

The old config used exponential backoff (fallbackRetryDelay: 30s with retryCount: 5). Consider if lychee supports exponential backoff or if 10 seconds is sufficient. For transient 502/503 errors, servers may need more time.

3. Missing Headers Configuration

The old config had httpHeaders for docs.github.com and github.com with Accept: text/html. Does lychee need this? GitHub sometimes requires proper Accept headers. Check if lychee handles this automatically.

4. Cache Key Strategy

Using github.sha means each commit gets a new cache, but restore-keys will restore from previous runs. This is good for CI efficiency. ✅

Optional enhancement: Consider adding branch to the cache key for PR-specific caching.

5. Testing Before Merge

Critical: Since this PR will correctly detect ~26 broken links:

This prevents blocking unrelated PRs while broken links are being fixed.


🔒 Security Considerations

✅ No security concerns

  • Uses official lychee-action from trusted source
  • No credential handling
  • Read-only operations

⚡ Performance Considerations

✅ Excellent improvement

  • 20x faster (12s vs 4+ min)
  • Caching reduces repeated link checks
  • Parallel checking built-in to lychee

📝 Minor Nits

  1. Verify .lychee.toml ends with a newline (required by project standards)
  2. Consider alphabetizing exclude patterns for easier maintenance
  3. Consider adding a header comment to .lychee.toml documenting the migration

✅ Final Verdict

LGTM with minor suggestions!

The migration is well-thought-out and solves a real problem. The PR is ready to merge once you:

  1. Verify trailing newline in .lychee.toml
  2. Run bundle exec rubocop (per CLAUDE.md)
  3. Decide on merge strategy vs Fix outdated paths after monorepo restructuring #2216 (fix links first or use fail: false temporarily)

Great work! This will significantly improve the reliability of link checking in CI.


🤖 Generated with Claude Code

ihabadham and others added 3 commits December 13, 2025 22:29
206 (Partial Content) was inherited from the old markdown-link-check
config but is unnecessary - no URLs in our docs return 206. Lychee
doesn't send Range headers, so we'd never trigger a 206 response.

Keeping the accept list minimal: 200 (OK) and 308 (Permanent Redirect).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Expand scope to check root *.md files (README, CHANGELOG, etc.)
  in addition to docs/ directory
- Increase retry resilience: 5 retries with 30s wait (was 3/10s)
  to match previous markdown-link-check behavior
- Document why medium.com is excluded (returns 403 for bots)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Config improvements:
- Expand scope to include react_on_rails_pro/docs/ and react_on_rails_pro/*.md
- Add path exclusions for node_modules and spec directories
- Add docs.google.com to URL exclusions (private docs require auth)
- Adjust retry config to 4 retries with 15s wait (balanced approach)

Fixed broken links in react_on_rails_pro/:
- CONTRIBUTING.md: Update helper.ts path after monorepo restructure
- CHANGELOG.md: Fix shakacode.com RSC tutorial URL
- docs/release-notes/4.0.md: Fix RSC tutorial relative links
- docs/release-notes/v4-react-server-components.md: Fix RSC tutorial link

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ihabadham
Copy link
Collaborator Author

Response to Claude Code Review Suggestions

We investigated each suggestion and addressed them as follows:

✅ Addressed

Suggestion Action Taken
1. Limited Scope Expanded to check docs/, *.md, react_on_rails_pro/docs/, react_on_rails_pro/*.md. Also added path exclusions for node_modules and spec directories.
3. Retry Configuration Changed to 4 retries with 15s wait - balanced between speed and resilience.
4. medium.com Context Added inline comment explaining why it's excluded (returns 403 for bots).

❌ Not Needed (Investigated)

Suggestion Why Not Needed
2. Missing 301/302 in accept Incorrect suggestion. Lychee follows redirects automatically via max_redirects (default 10). The accept list is for the final status after following redirects. Our CI output showed "🔀 54 Redirects" as successful.
2. Add 429 to accept Decided against. Our retries handle transient 429s. Persistent 429s (anti-bot) should fail so we know to add explicit exclusions.
5. GitHub Blob URLs (base) Only links starting with / are in docs/planning/ which is already excluded via exclude_path. Verified with grep -rE '\]\(/' docs/.
6. HTTP Headers Verified GitHub returns HTML without Accept header. Tested with curl -sI "https://github.com/shakacode/react_on_rails"content-type: text/html.

Additional Changes Made

  • Removed 206 from accept: No URLs in our docs return 206. Lychee doesn't send Range headers.
  • Added docs.google.com exclusion: Private docs require auth, will always 401.
  • Fixed 4 broken links in react_on_rails_pro/: Updated paths after monorepo restructure (helper.ts, RSC tutorial links).

All changes are in commits:

  • 06ce277 - Remove 206 from accept
  • 1b4d734 - Expand scope, document medium.com
  • e8acb5c - Full monorepo scope, fix broken links, add exclusions

@claude
Copy link

claude bot commented Dec 13, 2025

Code Review - PR #2219: Migrate from markdown-link-check to lychee

✅ Overall Assessment

This is an excellent migration that solves a critical CI reliability issue. The PR correctly addresses the silent failure problem described in #2217 and switches to a modern, faster, and more reliable link checker.


🎯 Strengths

1. Proper Root Cause Analysis

  • Correctly identified the ESM compatibility bug in markdown-link-check with Node.js 21
  • The PR description clearly explains why the old setup was failing silently
  • Good documentation of the timeline and issue progression

2. Smart Tool Choice

Lychee is a significantly better choice:

  • ✅ Built-in retry logic (no need for wretry.action wrapper)
  • ✅ Proper exit codes (exit 2 for link failures - no fragile string matching)
  • 12 seconds vs 4+ minutes for 550 links - massive performance improvement
  • ✅ Rust-based (no Node.js/Docker compatibility issues)
  • ✅ Built-in caching support

3. Clean Configuration Migration

The .lychee.toml properly migrates all important settings from the old config:

  • All exclusion patterns preserved
  • Retry logic properly configured
  • Timeout settings maintained
  • Good comments explaining each section

4. Workflow Improvements

  • Removed the fragile wretry.action wrapper (no longer needed)
  • Added proper caching with actions/cache@v4
  • Simpler, more maintainable workflow
  • Correct trigger paths updated to .lychee.toml

🔍 Code Quality Review

Workflow File (.github/workflows/check-markdown-links.yml)

✅ Good:

  • Clean removal of the wretry.action wrapper
  • Proper cache key strategy using github.sha with fallback
  • fail: true ensures CI fails on broken links
  • Path triggers correctly updated

⚠️ Considerations:

  1. Cache Key Strategy (line 30-31):
    The current approach creates a new cache entry for every commit. While lychee has a 1-day max age, GitHub Actions cache has a 7-day retention and 10GB limit. For a busy repo, this could accumulate many cache entries.

  2. Args Specification (line 36):
    Does *.md at the root catch all necessary files? The old config used folder-path: docs/ with max-depth: -1. Consider if you need recursive globbing to catch nested markdown files.

Lychee Configuration (.lychee.toml)

✅ Excellent:

  • Well-organized and commented
  • All critical exclusions migrated
  • Proper retry configuration (4 retries, 15s wait)
  • Cache enabled with sensible 1-day max age
  • include_fragments = false is smart (anchors are flaky)

⚠️ Potential Issues:

  1. Missing Exclusions (lines 16-33):
    I notice you added some new exclusions like medium.com and docs.google.com, but the old config had some GitHub URL patterns. Were these intentionally removed because the links are now fixed?

  2. Accept Codes (line 6):
    You are only accepting 200 (OK) and 308 (Permanent Redirect). The old config accepted 200 and 206 (Partial Content).

    Considerations:

    • Should you accept 206 (Partial Content)?
    • Should you accept other redirect codes like 301 or 302?
    • Lychee by default follows redirects and checks the final destination

    Recommendation: Consider adding common redirect codes or removing the accept parameter entirely to use lychee's defaults (which include all 2xx and 3xx codes).

  3. Exclude Path - spec/ (line 39):
    This excludes ALL spec files, including spec/dummy/ which might have documentation. Is this intentional?

  4. Missing replacementPatterns:
    The old config had a replacement pattern for relative links starting with /. Lychee supports remap for this. Should this be added, or were all relative links updated?

Documentation Link Fixes

✅ Good fixes:

  • react-server-components-tutorial.mdreact-server-components/tutorial.md (4 occurrences)
  • Fixed Pro package path references in CONTRIBUTING.md
  • Formatting consistency improvements

No issues found in the link corrections.

.gitignore

✅ Perfect: Added .lycheecache to gitignore (line 94)


🔒 Security Considerations

✅ No security concerns:

  • Using official lychee-action from trusted maintainer
  • No credential handling required
  • All configuration is transparent and secure

🧪 Testing Recommendations

Before Merging:

  1. Verify link detection works - Temporarily add a broken link to confirm CI catches it

  2. Test cache behavior - Verify cache is created and restored across commits

  3. Test locally:

    # Install lychee
    cargo install lychee  # or: brew install lychee
    
    # Run the same command as CI
    lychee --config .lychee.toml docs/ *.md react_on_rails_pro/docs/ react_on_rails_pro/*.md
  4. Performance verification - Confirm CI runs complete in ~12 seconds (not 4+ minutes)


📋 Suggested Improvements (Optional)

1. Consider Adding a Status Badge

Add to README.md to show link check status

2. Add Output to Workflow

The lychee-action supports format: markdown and jobSummary: true for better output

3. Document the Change

Consider adding a note to CLAUDE.md or CONTRIBUTING.md about the link checker for future contributors


🎯 Required Actions Before Merge

Critical (Must Fix):

None - the PR is functionally correct as-is.

Recommended (Should Consider):

  1. Verify the accept codes - Consider if 301/302/206 should be accepted
  2. Check if remap is needed for the old replacementPatterns behavior
  3. Confirm glob patterns catch all necessary markdown files
  4. Test locally to confirm broken links are detected

Nice to Have:

  1. Add job summary output
  2. Document lychee usage for contributors
  3. Optimize cache key strategy

✅ Approval Recommendation

APPROVE with minor suggestions

This PR solves a critical CI reliability issue and represents a significant improvement over the previous setup. The migration is well-executed, the configuration is solid, and the performance gains are substantial.

The suggested improvements are mostly optional optimizations and clarifications. The PR is ready to merge as-is if the author confirms:

  1. The accept codes are intentionally strict
  2. The glob patterns cover all necessary files
  3. The replacementPatterns behavior is no longer needed

Great work on diagnosing and fixing this issue! 🎉

The fact that this will initially fail CI is actually a feature, not a bug - it proves the link checker is now working correctly and catching the 26 broken links that the old setup missed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
.github/workflows/check-markdown-links.yml (1)

26-31: Optimize cache key strategy for better hit rates.

The cache key cache-lychee-${{ github.sha }} creates a unique cache entry per commit, which means cache hits will only occur if the same commit runs twice. This defeats the primary benefit of caching. Consider using a key that persists across commits unless the configuration changes:

- key: cache-lychee-${{ github.sha }}
+ key: cache-lychee-${{ hashFiles('.lychee.toml') }}

This approach:

  • Creates a new cache entry only when .lychee.toml changes
  • Reuses the same cache across commits within the same configuration
  • Significantly improves cache hit rates and CI performance
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae99a30 and e8acb5c.

📒 Files selected for processing (6)
  • .github/workflows/check-markdown-links.yml (2 hunks)
  • .lychee.toml (1 hunks)
  • react_on_rails_pro/CHANGELOG.md (1 hunks)
  • react_on_rails_pro/CONTRIBUTING.md (11 hunks)
  • react_on_rails_pro/docs/release-notes/4.0.md (1 hunks)
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • react_on_rails_pro/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .lychee.toml
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rb,js,ts,jsx,tsx,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{rb,js,ts,jsx,tsx,json,yml,yaml,md}: ALWAYS run bundle exec rubocop and fix ALL violations before every commit/push
ALWAYS ensure files end with a newline character before committing

Files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
**/*.{js,ts,jsx,tsx,json,css,scss,md}

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS let Prettier handle ALL formatting - never manually format code

Files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
**/.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

Changes to CI workflows require comprehensive local testing BEFORE pushing to avoid CI failure cycles

Files:

  • .github/workflows/check-markdown-links.yml
🧠 Learnings (30)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for open-source features, bug fixes, breaking changes, deprecations, and performance improvements using format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)`
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for open-source features, bug fixes, breaking changes, deprecations, and performance improvements using format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)`

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • react_on_rails_pro/docs/release-notes/4.0.md
  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to /CHANGELOG.md,/CHANGELOG_PRO.md : Do NOT add changelog entries for: linting, formatting, refactoring, tests, or documentation-only fixes

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to react_on_rails_pro/**/*.{js,ts,jsx,tsx,json,css,scss} : The `react_on_rails_pro/` directory has its own Prettier/ESLint configuration separate from the root; CI lints both directories separately

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for Pro-only features, bug fixes, and improvements using the same format: `[PR number](URL) by [username](URL)`

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to **/*.{rb,js,ts,jsx,tsx,json,yml,yaml,md} : ALWAYS run `bundle exec rubocop` and fix ALL violations before every commit/push

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to lib/react_on_rails/**/*.rb : Create corresponding RBS signature files in `sig/react_on_rails/` for new Ruby files and add them to Steepfile for type checking

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Monorepo contains separate open-source and Pro packages; changes affecting both require updating both `/CHANGELOG.md` and `/CHANGELOG_PRO.md`

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to lib/react_on_rails/**/*.rb : Engine code has limited access to host application; use `Rails.root` for host app paths and `ReactOnRails.configure` for engine configuration

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to lib/generators/react_on_rails/**/*.rb : Generators run in host app context, not engine context; do not assume host app structure (e.g., `app/javascript/` may not exist in older apps)

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to spec/dummy/**/*.{spec,test}.{js,ts} : Playwright E2E tests use `cypress-on-rails` gem for Rails integration; use helpers like `app('clean')`, `appEval()`, and `appFactories()` for test setup instead of UI automation

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to **/.github/workflows/*.yml : Changes to CI workflows require comprehensive local testing BEFORE pushing to avoid CI failure cycles

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
  • .github/workflows/check-markdown-links.yml
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom app commands for Playwright E2E tests in this directory using the `command` syntax provided by `cypress-on-rails` gem

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Prefer local testing over CI iteration to save time (15-minute rule: if 15 more minutes of local testing would prevent a CI failure, spend the time locally first)

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-01-23T18:20:45.824Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1687
File: spec/dummy/package.json:0-0
Timestamp: 2025-01-23T18:20:45.824Z
Learning: When adding or updating dependencies in spec/dummy/package.json, maintain version consistency with other package.json files in the codebase to avoid potential version conflicts.

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to package.json,pnpm-lock.yaml : Test build scripts after modifying package.json or dependencies: run `pnpm run prepack` and `pnpm run yalc.publish` to verify build pipeline

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to spec/dummy/**/*.{js,ts} : Install Playwright browsers via `cd spec/dummy && pnpm playwright install --with-deps` before running E2E tests

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-12-10T09:27:45.105Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-10T09:27:45.105Z
Learning: Applies to spec/dummy/config/webpack/**/*.js : When debugging Webpack configuration issues, create temporary debug scripts to inspect rules and loaders; clean them up before committing

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • react_on_rails_pro/CONTRIBUTING.md
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • react_on_rails_pro/docs/release-notes/v4-react-server-components.md
🪛 markdownlint-cli2 (0.18.1)
react_on_rails_pro/CONTRIBUTING.md

34-34: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


48-48: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: claude-review
  • GitHub Check: markdown-link-check
🔇 Additional comments (4)
.github/workflows/check-markdown-links.yml (2)

9-9: Correct trigger paths for .lychee.toml.

Adding .lychee.toml to workflow triggers is the right call—the workflow should re-run whenever the configuration changes.

Also applies to: 14-14


33-36: Verify comprehensive local testing and path coverage before landing.

The migration from markdown-link-check to lychee is sound. However, please confirm:

  1. Local testing: Per workflow guidelines, comprehensive local testing should be performed before pushing. Have you verified the lychee setup works locally with the specified paths and .lychee.toml configuration?

  2. Path coverage: Verify that the specified paths (docs/, *.md, react_on_rails_pro/docs/, react_on_rails_pro/*.md) cover all markdown files in the repository that may contain external links.

  3. Known broken links: The PR notes ~26 broken URLs will be detected by lychee (and should fail CI as expected). Confirm the team has a plan to fix these separately and that this is an acceptable interim state.

react_on_rails_pro/docs/release-notes/v4-react-server-components.md (1)

48-48: The relative link path is correct. The target file exists at react_on_rails_pro/docs/react-server-components/tutorial.md, and the relative path ../react-server-components/tutorial.md properly navigates from the release-notes/ directory. No action required.

react_on_rails_pro/docs/release-notes/4.0.md (1)

14-14: > Likely an incorrect or invalid review comment.

Comment on lines 34 to 36
#### The seven rules of a great git commit message

> Keep in mind: This has all been said before.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix heading level increment (MD001): use ### instead of ####.

Line 34 jumps from ## Commit Messages to an H4. This trips markdownlint and makes the doc hierarchy uneven.

-#### The seven rules of a great git commit message
+### The seven rules of a great git commit message
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### The seven rules of a great git commit message
> Keep in mind: This has all been said before.
### The seven rules of a great git commit message
> Keep in mind: This has all been said before.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

34-34: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

🤖 Prompt for AI Agents
In react_on_rails_pro/CONTRIBUTING.md around lines 34 to 36, the heading "The
seven rules of a great git commit message" is using an H4 (####) immediately
after the H2 "Commit Messages", which violates markdown heading hierarchy
(MD001); change that H4 to an H3 (###) so the document uses an incrementally
nested heading level and resolves the markdownlint error.

Comment on lines +48 to 49
When making doc changes, we want the change to work on both https://www.shakacode.com/react-on-rails-pro/docs/ and when browsing the GitHub repo.
The issue is that the Shakacode site is generated only from files in [`docs`](./docs), so any references from them to non-doc files must use the full GitHub URL.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid bare URL (MD034): turn the site URL into a link.

Line 48 uses a bare URL; prefer a markdown link for consistency and lint compliance.

-When making doc changes, we want the change to work on both https://www.shakacode.com/react-on-rails-pro/docs/ and when browsing the GitHub repo.
+When making doc changes, we want the change to work on both [shakacode.com docs](https://www.shakacode.com/react-on-rails-pro/docs/) and when browsing the GitHub repo.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

48-48: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In react_on_rails_pro/CONTRIBUTING.md around lines 48 to 49, replace the bare
URL "https://www.shakacode.com/react-on-rails-pro/docs/" with a markdown link to
avoid MD034; update the sentence to use the link text (e.g. [Shakacode React on
Rails Pro docs]) and the URL in parentheses so the site and repo rendering are
consistent and linter-compliant.

Comment on lines +181 to 184
> [!NOTE] > `yalc` makes the NPM package available globally on the machine.
> So, if you have the repo checked out more than once to compare behavior between branches,
> make sure to run `yarn install` every time you switch to a new copy.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix malformed NOTE admonition so it renders correctly.

Line 181 currently has > [!NOTE] > ..., which won’t render as an admonition (and may collapse into a single quoted line). Use a proper multi-line blockquote:

-> [!NOTE] > `yalc` makes the NPM package available globally on the machine.
-> So, if you have the repo checked out more than once to compare behavior between branches,
-> make sure to run `yarn install` every time you switch to a new copy.
+> [!NOTE]
+> `yalc` makes the NPM package available globally on the machine.
+> So, if you have the repo checked out more than once to compare behavior between branches,
+> make sure to run `yarn install` every time you switch to a new copy.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> [!NOTE] > `yalc` makes the NPM package available globally on the machine.
> So, if you have the repo checked out more than once to compare behavior between branches,
> make sure to run `yarn install` every time you switch to a new copy.
> [!NOTE]
> `yalc` makes the NPM package available globally on the machine.
> So, if you have the repo checked out more than once to compare behavior between branches,
> make sure to run `yarn install` every time you switch to a new copy.
🤖 Prompt for AI Agents
In react_on_rails_pro/CONTRIBUTING.md around lines 181 to 184, the NOTE
admonition is malformed as it currently reads "`> [!NOTE] > ...`"; remove the
extra `>` after `[!NOTE]` and convert the following lines into a proper
multi-line blockquote by making the first line just `> [!NOTE]` and prefixing
each subsequent line of the note with `> ` so the admonition renders correctly.

- Fix reactrails.com link in PROJECTS.md (http → https)
- Remove caseflow link from PROJECTS.md (404 - repo no longer public)
- Add URL exclusions for sites that block automated requests:
  - blog.shakacode.com, foxford.ru, airgoat.com, first.io,
    estately.com, hiring.careerbuilder.com
- Exclude SUMMARY.md from checking (GitBook TOC with old doc
  structure links, intentionally kept for redirects)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai coderabbitai bot added the bug label Dec 13, 2025
@claude
Copy link

claude bot commented Dec 13, 2025

Code Review: Migrate from markdown-link-check to lychee

✅ Overall Assessment

This is an excellent migration that replaces a broken link-checking setup with a modern, reliable alternative. The changes are well-executed and address a critical CI issue where link checking was silently failing.


🎯 Strengths

1. Problem Diagnosis & Solution Choice

  • Clear identification of the root cause (Node.js 21 ESM compatibility bug)
  • Excellent choice of replacement tool (lychee) with built-in retry, proper exit codes, and faster execution
  • Measured performance improvement: ~12 seconds vs 4+ minutes

2. Configuration Migration Quality

The .lychee.toml configuration is well-structured:

  • ✅ Comprehensive exclude patterns migrated from old config
  • ✅ Good comments explaining each exclusion
  • ✅ Proper retry configuration (max_retries=4, retry_wait_time=15)
  • ✅ Caching enabled for faster repeated runs
  • ✅ Sensible timeout (20s) and accept codes

3. Workflow Improvements

  • ✅ Simpler, cleaner GitHub Actions workflow
  • ✅ Added caching for .lycheecache
  • ✅ Proper fail mode (fail: true)
  • ✅ Updated trigger paths to use .lychee.toml

4. Documentation Updates

  • ✅ Fixed broken link references in Pro changelog and docs
  • ✅ Updated react-server-components-tutorial.mdreact-server-components/tutorial.md
  • ✅ Protocol change from http:// to https:// for reactrails.com
  • ✅ Cleaned up PROJECTS.md (removed caseflow reference)
  • ✅ Fixed formatting in CONTRIBUTING.md

🔍 Detailed Review

Configuration File (.lychee.toml)

Good:

  • Excludes are comprehensive and well-documented
  • Cache configuration is appropriate (1 day max age)
  • include_fragments = false avoids flaky anchor checks

Minor suggestion:

  • Consider documenting the accept = ["200", "308"] choice more explicitly. Why 308 specifically? (Permanent Redirect is good, but worth a comment)

Workflow Changes

Good:

  • Cache key uses github.sha with restore fallback
  • Action version pinned to v2 (good practice)
  • Args clearly specify which paths to check

Potential improvement:

  • The workflow uses lycheeverse/lychee-action@v2 which is good, but consider pinning to a specific SHA for maximum security (as mentioned in CLAUDE.md for critical workflows)

Documentation Changes

Excellent:

  • All link fixes appear correct
  • http://https:// upgrade for reactrails.com is a security improvement
  • Removed broken/outdated references appropriately

🚨 Considerations

1. Expected CI Failures

The PR description correctly notes this will fail CI due to ~26 broken links. This is expected behavior and shows the tool is working correctly. The old setup was hiding these failures.

Recommendation: Merge this PR once the link fixes from related PRs are applied, OR update .lychee.toml to temporarily exclude known-broken URLs with comments indicating they need fixing.

2. Exclusion Pattern Coverage

The exclusions look good, but verify these patterns are still needed:

  • ^https://your-shared-addr\.c9users\.io - Cloud9 URLs (likely still valid)
  • ^https://hichee\.com - Is this still referenced anywhere?

3. Gitignore Addition

✅ Good addition of .lycheecache to .gitignore


📋 Testing Recommendations

Before merging:

  1. Verify exclusion patterns work:

    # Install lychee locally
    # Run against docs to confirm excluded URLs are skipped
    lychee --config .lychee.toml docs/ *.md react_on_rails_pro/docs/ react_on_rails_pro/*.md
  2. Confirm cache behavior:

    • First run should check all links
    • Second run should be faster due to caching
    • Cache should respect the 1-day max age
  3. Check that actual broken links are detected:

    • Intentionally add a broken link to a test file
    • Verify lychee catches it with proper exit code 2

🎨 Code Quality

  • Best Practices: ✅ Follows project conventions
  • Security: ✅ No security concerns
  • Performance: ✅ Major improvement (12s vs 4+ minutes)
  • Maintainability: ✅ Cleaner, more maintainable than wrapper-based approach
  • Documentation: ✅ Well-commented configuration

📝 Minor Nitpicks

  1. PROJECTS.md line 44: Removed the caseflow project reference. Was this intentional or related to link checking? (Seems unrelated to the migration)

  2. CONTRIBUTING.md formatting: The changes improve consistency (e.g., adding missing blank lines), but some are just whitespace/formatting. Consider using rake autofix to ensure Prettier handles all formatting automatically per CLAUDE.md.


✅ Approval Recommendation

APPROVE with minor suggestions

This PR:

  • ✅ Solves a critical silent failure in CI
  • ✅ Improves performance dramatically
  • ✅ Uses a better-maintained tool with proper exit codes
  • ✅ Includes comprehensive configuration migration
  • ✅ Fixes related documentation issues

The expected CI failures are not a blocker—they prove the tool is working correctly.


🚀 Next Steps

  1. Merge related link-fixing PRs first (if any)
  2. OR: Add temporarily broken URLs to exclusions with # TODO: fix in issue #XXX comments
  3. Verify lychee workflow runs successfully after known broken links are fixed
  4. Merge this PR

Great work on identifying and fixing this silent failure! 🎉

Returns 403 for automated requests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link checker CI passes despite broken links on master

4 participants