Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 193 additions & 13 deletions .github/workflows/linkcheck-pr.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,213 @@
name: Lychee PR link checker
name: Link checker - PR changed files

# Avoid collisions by ensuring only one run per ref
concurrency:
group: linkcheck-pr-${{ github.ref_name }}
cancel-in-progress: false

on:
workflow_dispatch:
deployment_status:
pull_request:
types: [opened, synchronize, reopened]
paths:
- '**.md'
- '**.mdx'

permissions:
contents: read
deployments: read
pull-requests: write

jobs:
linkChecker:
runs-on: ubuntu-latest

# Only run when Mintlify PR deployment succeeds
# Run on: manual trigger, successful Mintlify deployment, or PR events (for forks)
if: |
github.event.deployment_status.state == 'success' &&
github.event.deployment.environment == 'staging' &&
contains(github.event.deployment_status.creator.login, 'mintlify') &&
contains(github.event.deployment_status.environment_url, 'mintlify')
github.event_name == 'workflow_dispatch' ||
github.event_name == 'pull_request' ||
(github.event_name == 'deployment_status' &&
github.event.deployment_status.state == 'success' &&
github.event.deployment.environment == 'staging' &&
contains(github.event.deployment_status.creator.login, 'mintlify') &&
contains(github.event.deployment_status.environment_url, 'mintlify'))

steps:
# check URLs with Lychee
- uses: actions/checkout@v6
with:
# Needed to diff base..head for the associated PR
fetch-depth: 0

- name: Resolve PR and deployment URL
id: pr-context
if: github.event_name == 'deployment_status'
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const sha = context.payload.deployment?.sha;
const deployUrl =
context.payload.deployment_status?.environment_url ||
context.payload.deployment_status?.target_url ||
'';

core.info(`Deployment SHA: ${sha}`);
core.info(`Deployment URL: ${deployUrl}`);

// Find PR(s) associated with this deployment commit SHA
const prsResp = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: sha,
});

const pr = prsResp.data?.[0];
if (!pr) {
core.warning(`No PR associated with commit ${sha}. Skipping linkcheck + PR comment.`);
core.setOutput('pr_number', '');
core.setOutput('deploy_url', deployUrl);
return;
}

core.info(`Associated PR: #${pr.number} (${pr.html_url})`);
core.setOutput('pr_number', String(pr.number));
core.setOutput('base_sha', pr.base.sha);
core.setOutput('head_sha', pr.head.sha);
core.setOutput('deploy_url', deployUrl);

- name: Get changed documentation files
id: changed-files
if: steps.pr-context.outputs.pr_number != '' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request'
uses: tj-actions/changed-files@v47.0.1
with:
base_sha: ${{ steps.pr-context.outputs.base_sha || github.event.pull_request.base.sha }}
sha: ${{ steps.pr-context.outputs.head_sha || github.event.pull_request.head.sha }}
files: |
**/*.md
**/*.mdx

- name: Link Checker
id: lychee
if: steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'workflow_dispatch'
uses: lycheeverse/lychee-action@v2
with:
args: "--threads 5 --max-retries 5 --retry-wait-time 2 --include '^https?://' --include '^http?://' --base-url='${{ github.event.deployment_status.environment_url }}' '${{ github.event.deployment_status.environment_url }}'"
format: markdown
fail: false
env:
# to be used in case rate limits are surpassed
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
# Don't fail if no files to check
failIfEmpty: false
# Output format for reports
format: markdown
# GitHub token for API rate limiting
token: ${{ secrets.GITHUB_TOKEN }}
# Override base_url with deployment URL (if available) or use production
# For forks without Mintlify preview: checks against production site
args: >-
--base-url ${{ steps.pr-context.outputs.deploy_url || 'https://docs.wandb.ai' }}
${{ steps.changed-files.outputs.all_changed_files || '.' }}

- name: Comment PR with link check results
if: (steps.pr-context.outputs.pr_number != '' || github.event_name == 'pull_request') && (steps.changed-files.outputs.any_changed == 'true' || github.event_name == 'workflow_dispatch')
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const identifier = '<!-- lychee-link-checker-comment -->';

let commentBody = identifier + '\n';

const isFork = context.payload.pull_request?.head?.repo?.fork || false;
const deployUrl = '${{ steps.pr-context.outputs.deploy_url }}';

if (${{ steps.lychee.outputs.exit_code }} === 0) {
// Success - no broken links
commentBody += '## 🔗 Link Checker Results\n\n';
commentBody += '✅ **All links are valid!**\n\n';
commentBody += 'No broken links were detected in the changed files.\n';
if (isFork && !deployUrl) {
commentBody += '\n_Note: Checked against production site (https://docs.wandb.ai) since preview deployments are not available for forks._\n';
}

// Check if there were redirects in the report
try {
const report = fs.readFileSync('./lychee/out.md', 'utf8');
if (report.includes('Redirect') || report.includes('redirect')) {
commentBody += '\n\n> [!TIP]\n';
commentBody += '> **Redirects detected**: If you see redirects for internal docs.wandb.ai links, check if they have trailing slashes.\n';
commentBody += '> \n';
commentBody += '> Mintlify automatically removes trailing slashes, causing redirects like:\n';
commentBody += '> - `https://docs.wandb.ai/models/` → `https://docs.wandb.ai/models`\n';
commentBody += '> \n';
commentBody += '> **Fix**: Remove trailing slashes from links to avoid unnecessary redirects.\n';
}
} catch (e) {
// Ignore if report file doesn't exist
}
} else {
// Issues found - include report
const report = fs.readFileSync('./lychee/out.md', 'utf8');

commentBody += '## 🔗 Link Checker Results\n\n';
commentBody += '> [!NOTE]\n';
if (isFork && !deployUrl) {
commentBody += '> This PR is from a fork, so links were checked against the **production site** (https://docs.wandb.ai).\n';
commentBody += '> \n';
commentBody += '> Links to **newly created files** in this PR will be reported as broken until the PR is merged.\n';
} else {
commentBody += '> Links to **newly created files** in this PR may be reported as broken because this checks links against the **preview deployment**.\n';
}
commentBody += '> \n';
commentBody += '> Warnings about **new** files in this PR can be safely ignored.\n\n';

// Add trailing slash tip if redirects are present
if (report.includes('Redirect') || report.includes('redirect')) {
commentBody += '> [!TIP]\n';
commentBody += '> **Redirects detected**: If you see redirects for internal docs.wandb.ai links, check if they have trailing slashes.\n';
commentBody += '> \n';
commentBody += '> Mintlify automatically removes trailing slashes, causing redirects like:\n';
commentBody += '> - `https://docs.wandb.ai/models/` → `https://docs.wandb.ai/models`\n';
commentBody += '> - `/weave/quickstart/` → `/weave/quickstart`\n';
commentBody += '> \n';
commentBody += '> **Fix**: Remove trailing slashes from links to avoid unnecessary redirects.\n\n';
}

commentBody += '---\n\n';
commentBody += report;
}

// Determine PR number
const prNumber = Number('${{ steps.pr-context.outputs.pr_number }}') ||
context.payload.pull_request?.number;

if (!prNumber) {
core.info('No PR number available, skipping comment');
return;
}

// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existingComment = comments.find(comment =>
comment.body?.includes(identifier) && comment.user?.login === 'github-actions[bot]'
);

if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: commentBody
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: commentBody
});
}
30 changes: 22 additions & 8 deletions .github/workflows/linkcheck-prod.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
name: Lychee production link checker
name: Link checker - production site

on:
repository_dispatch:
workflow_dispatch:
schedule:
- cron: "5 0 1 * *" # In UTC, currently 12:05 AM on the 1st of each month
- cron: "5 0 1 * *" # Monthly on the 1st at 12:05 AM UTC

jobs:
linkChecker:
runs-on: ubuntu-latest
permissions:
issues: write # required for peter-evans/create-issue-from-file
issues: write # Required for creating issues
steps:
# check URLs with Lychee
- uses: actions/checkout@v6

- name: Download and parse sitemap
run: |
echo "Fetching sitemap from https://docs.wandb.ai/sitemap.xml..."
curl -s https://docs.wandb.ai/sitemap.xml | \
grep -o '<loc>[^<]*</loc>' | \
sed 's/<loc>//g; s/<\/loc>//g' > urls.txt

URL_COUNT=$(wc -l < urls.txt | tr -d ' ')
echo "Found ${URL_COUNT} URLs in sitemap"

# Show first few URLs for verification
echo "Sample URLs:"
head -5 urls.txt

- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@v2
with:
args: "--threads 5 --max-retries 5 --retry-wait-time 2 --include '^https?://' --include '^http?://' --base-url='http://docs.wandb.ai' 'http://docs.wandb.ai'"
# Configuration is in lychee.toml
# Check all URLs from sitemap
args: "urls.txt"
output: ./lychee-report.md
format: markdown
fail: false
env:
# to be used in case rate limits are surpassed
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Create Issue From File
if: steps.lychee.outputs.exit_code != 0
Expand Down
53 changes: 53 additions & 0 deletions lychee.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Lychee link checker configuration
# See https://lychee.cli.rs/guides/config/ for full documentation

# Base URL for resolving relative links
# This will be overridden at runtime by the workflows
base_url = 'https://docs.wandb.ai'

# Don't check heading anchors (fragments) - Mintlify generates these dynamically
include_fragments = false

# Suppress progress bars in CI for cleaner logs
no_progress = true

# Exclude private/internal network links
exclude_all_private = true

# Retry configuration for handling transient failures
max_retries = 5
retry_wait_time = 2

# Accept these HTTP status codes as valid
accept = [
200, # OK
429, # Too Many Requests (rate limit - treat as success)
]

# Only check HTTP/HTTPS URLs
scheme = [
"https",
"http",
]

# Only check URLs matching these patterns (required when checking URL lists)
include = [
'^https?://', # Match all http:// and https:// URLs
]

# Logging verbosity
verbose = "info"

# URL patterns to exclude from checking
exclude = [
# Exclude images - Mintlify rewrites paths during build
'\.(png|jpg|jpeg|gif|svg|webp|ico)$',
]

# File/directory paths to exclude from checking
exclude_path = [
# Exclude non-documentation directories
"scripts",
"node_modules",
".github",
]
23 changes: 23 additions & 0 deletions training/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ https://api.training.wandb.ai/v1

### models

- **[POST /v1/preview/models](https://docs.wandb.ai/training/api-reference/models/create-model-v1-preview-models)** - Create Model
- **[DELETE /v1/preview/models/{model_id}](https://docs.wandb.ai/training/api-reference/models/delete-model-v1-preview-models--model-id-)** - Delete Model
- **[DELETE /v1/preview/models/{model_id}/checkpoints](https://docs.wandb.ai/training/api-reference/models/delete-model-checkpoints-v1-preview-models--model-id--checkpoints)** - Delete Model Checkpoints
- **[GET /v1/preview/models/{model_id}/checkpoints](https://docs.wandb.ai/training/api-reference/models/list-model-checkpoints-v1-preview-models--model-id--checkpoints)** - List Model Checkpoints
- **[POST /v1/preview/models/{model_id}/log](https://docs.wandb.ai/training/api-reference/models/log-v1-preview-models--model-id--log)** - Log

### training-jobs

- **[POST /v1/preview/training-jobs](https://docs.wandb.ai/training/api-reference/training-jobs/create-training-job-v1-preview-training-jobs)** - Create Training Job
- **[GET /v1/preview/training-jobs/{training_job_id}](https://docs.wandb.ai/training/api-reference/training-jobs/get-training-job-v1-preview-training-jobs--training-job-id-)** - Get Training Job
- **[GET /v1/preview/training-jobs/{training_job_id}/events](https://docs.wandb.ai/training/api-reference/training-jobs/get-training-job-events-v1-preview-training-jobs--training-job-id--events)** - Get Training Job Events

### health

- **[GET /v1/health](https://docs.wandb.ai/training/api-reference/health/health-check-v1-health)** - Health Check
- **[GET /v1/system-check](https://docs.wandb.ai/training/api-reference/health/system-check-v1-system-check)** - System Check
### chat-completions

- **[POST /v1/chat/completions](https://docs.wandb.ai/training/api-reference/chat-completions/create-chat-completion-v1-chat-completions)** - Create Chat Completion
- **[POST /v1/chat/completions/](https://docs.wandb.ai/training/api-reference/chat-completions/create-chat-completion-v1-chat-completions-)** - Create Chat Completion

### models

- **[POST /v1/preview/models](https://docs.wandb.ai/training/api-reference/models/create-model-v1-preview-models)** - Create Model
- **[DELETE /v1/preview/models/{model_id}/checkpoints](https://docs.wandb.ai/training/api-reference/models/delete-model-checkpoints-v1-preview-models--model-id--checkpoints)** - Delete Model Checkpoints
- **[GET /v1/preview/models/{model_id}/checkpoints](https://docs.wandb.ai/training/api-reference/models/list-model-checkpoints-v1-preview-models--model-id--checkpoints)** - List Model Checkpoints
Expand Down
Loading