Files
coder/.github/workflows/weekly-docs.yaml
T
Nick Vigilante c13dd06d2f ci(.github/workflows): disable audit-docs-paths pending cross-repo auth (#26571)
The `audit-docs-paths` job in `weekly-docs.yaml` fetches a config file
from a private upstream source. An anonymous read returns 404 and the
job fails on every weekly run, firing a misleading "Stale docs paths
found in site/src/" Slack notification (a pre-existing bug in the
notification copy, tracked separately).

We originally tried to authenticate the fetch with the existing CI token
used for cross-repo work (the same one used by `contrib.yaml`), but that
token does not have read access to the upstream source. The proper fix
is a GitHub App scoped to cross-repo `Contents: Read`; the docs team is
tracking the App provisioning internally.

Until the App is provisioned, this PR disables the job behind a
`vars.AUDIT_DOCS_PATHS_ENABLED` repository variable. The variable is
unset, so the job skips on the weekly cron and on `workflow_dispatch`.
The other two jobs in this workflow (`prepare-linkspector-browser`,
`check-docs`) keep running normally, so docs PRs still get link-checked.

Re-enabling once the App is provisioned is a one-line change: set
`AUDIT_DOCS_PATHS_ENABLED` to `'true'` on this repo, no workflow edit
required.

<details>
<summary>Investigation log (why the App is needed)</summary>

Initial attempt (commits `5cca548`, `fc0ff59`, now discarded)
authenticated the fetch via the GitHub Contents API with `Accept:
application/vnd.github.raw` and an existing CI token already used for
cross-repo writes. `coder-agents-review` approved that approach in Round
2 ([review
4546533491](https://github.com/coder/coder/pull/26571#pullrequestreview-4546533491)),
and all 29 CI checks passed.

Validation via `workflow_dispatch` (run
[27973114839](https://github.com/coder/coder/actions/runs/27973114839))
failed at the fetch step with `curl: (22) The requested URL returned
error: 404`. The bare `curl` against the same URL with a personal access
token returned HTTP 200 and valid JSON, so the call shape was correct;
the CI token just lacks the necessary scope on the upstream source. The
Contents API returns 404 (not 403) when a token cannot see a private
repository, which is why the original failure mode was hard to
attribute.

Options considered:

1. **Extend the existing CI token** to include the missing read access.
Cheapest in lines of code, but the token is org-CI-owned and changing
its scope has blast radius beyond this job.
2. **New fine-grained PAT.** Tightest scope, but PATs are user-owned. If
the issuing user leaves the org, the token auto-revokes and the audit
silently breaks again, which is exactly the failure mode this PR is
trying to make less likely.
3. **GitHub App owned by the org.** Tied to the org, not a user;
survives staff turnover; least-privileged per repo. Heaviest setup
because creation, installation, and secret provisioning all need org
admin.

Option 3 is the right long-term answer but is not same-day. Disabling
the job is the smallest change that stops the noise immediately, and the
feature-flag variable keeps the re-enable path to one step.

</details>

<details>
<summary>Validation</summary>

* `actionlint` clean on `.github/workflows/weekly-docs.yaml`.
* Branch passed all 29 CI checks under the previous authentication
approach; this revision is strictly smaller (one job-level `if` guard +
comments), no new failure surface introduced.
* The disable cannot be tested end-to-end without merging, since the
affected job runs on `schedule` / `workflow_dispatch` against `main`.
Once merged: confirm the next weekly run (or a manual
`workflow_dispatch`) shows `audit-docs-paths` as skipped, with no Slack
notification.

</details>

---

> Generated by [Coder Agents](https://coder.com) on behalf of
@nickvigilante.
2026-06-22 15:45:41 -04:00

207 lines
8.2 KiB
YAML

name: weekly-docs
# runs every monday at 9 am
on:
schedule:
- cron: "0 9 * * 1"
workflow_dispatch: # allows to run manually for testing
pull_request:
branches:
- main
paths:
- "docs/**"
permissions:
contents: read
jobs:
prepare-linkspector-browser:
# later versions of Ubuntu have disabled unprivileged user namespaces, which are required by the action
runs-on: ubuntu-22.04
permissions:
contents: read
env:
CHROME_BUILD_ID: "145.0.7632.77"
outputs:
browser-cache-key: ${{ steps.browser-versions.outputs.cache-key }}
chrome-path: ${{ steps.install-chrome.outputs.path }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "node npm:@puppeteer/browsers"
- name: Get browser versions
id: browser-versions
run: |
set -euo pipefail
installer_version="$(mise current npm:@puppeteer/browsers)"
echo "cache-key=puppeteer-${RUNNER_OS}-${RUNNER_ARCH}-browsers-${installer_version}-chrome-${CHROME_BUILD_ID}" >> "$GITHUB_OUTPUT"
- name: Restore Puppeteer browser cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/puppeteer
key: ${{ steps.browser-versions.outputs.cache-key }}
- name: Install Linkspector Chrome
id: install-chrome
run: |
set -euo pipefail
chrome_path="$(browsers install "chrome@${CHROME_BUILD_ID}" --path "${HOME}/.cache/puppeteer" --format '{{path}}')"
echo "path=${chrome_path}" >> "$GITHUB_OUTPUT"
check-docs:
needs: prepare-linkspector-browser
# later versions of Ubuntu have disabled unprivileged user namespaces, which are required by the action
runs-on: ubuntu-22.04
permissions:
pull-requests: write # required to post PR review comments by the action
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Rewrite same-repo links for PR branch
if: github.event_name == 'pull_request'
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# Rewrite same-repo blob/tree main links to the PR head SHA
# so that files or directories introduced in the PR are
# reachable during link checking.
{
echo 'replacementPatterns:'
echo " - pattern: \"https://github.com/coder/coder/blob/main/\""
echo " replacement: \"https://github.com/coder/coder/blob/${HEAD_SHA}/\""
echo " - pattern: \"https://github.com/coder/coder/tree/main/\""
echo " replacement: \"https://github.com/coder/coder/tree/${HEAD_SHA}/\""
} >> .github/.linkspector.yml
# TODO: Remove this workaround once action-linkspector sets
# package-manager-cache: false in its internal setup-node step.
# See: https://github.com/UmbrellaDocs/action-linkspector/issues/54
- name: Enable corepack and create pnpm store
run: |
corepack enable pnpm
mkdir -p "$(pnpm store path --silent)"
- name: Restore Puppeteer browser cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/puppeteer
key: ${{ needs.prepare-linkspector-browser.outputs.browser-cache-key }}
- name: Check Markdown links
uses: umbrelladocs/action-linkspector@036f295d12b67b0c4b445bc83db0538afb78db69 # v1.5.2
id: markdown-link-check
# checks all markdown files from /docs including all subfolders
env:
# Use the Chrome build prepared from mise-pinned Puppeteer instead
# of letting linkspector download a mutable browser at runtime.
# See: https://github.com/UmbrellaDocs/action-linkspector/issues/62
PUPPETEER_EXECUTABLE_PATH: ${{ needs.prepare-linkspector-browser.outputs.chrome-path }}
with:
# On PRs, use github-pr-review for inline comments. On schedule/dispatch,
# use local so reviewdog actually reports failures instead of silently
# exiting 0 (github-pr-review requires a PR context).
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'local' }}
config_file: ".github/.linkspector.yml"
fail_on_error: "true"
filter_mode: ${{ github.event_name == 'pull_request' && 'file' || 'nofilter' }}
- name: Send Slack notification
if: failure() && github.event_name != 'pull_request'
run: |
curl \
-X POST \
-H 'Content-type: application/json' \
-d '{"text":":warning: *Broken links found in the documentation.*\nPlease check the logs: '"${LOGS_URL}"'"}' "${{ secrets.DOCS_LINK_SLACK_WEBHOOK }}"
echo "Sent Slack notification"
env:
LOGS_URL: https://github.com/coder/coder/actions/runs/${{ github.run_id }}
audit-docs-paths:
# Disabled by default: this audit fetches a config file from a private
# upstream source and the workflow currently lacks the credentials to
# read it. Pending provisioning of a dedicated GitHub App with the
# required cross-repo Contents: Read. To re-enable once the App
# credentials are in place, set the repository variable
# AUDIT_DOCS_PATHS_ENABLED to 'true'.
if: vars.AUDIT_DOCS_PATHS_ENABLED == 'true'
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Check for audit script
id: check-script
run: |
if [ ! -f site/scripts/audit-docs-paths.mjs ]; then
echo "::notice::Audit script not yet available (pending PR #25740). Skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
fi
- name: Set up mise tools
if: steps.check-script.outputs.skip != 'true'
uses: ./.github/actions/setup-mise
with:
install-args: "node"
- name: Fetch redirects.json
if: steps.check-script.outputs.skip != 'true'
run: |
curl -sfL \
https://raw.githubusercontent.com/coder/coder.com/refs/heads/main/redirects.json \
-o /tmp/redirects.json
- name: Audit TS/TSX docs paths against redirects
if: steps.check-script.outputs.skip != 'true'
run: |
node site/scripts/audit-docs-paths.mjs \
--redirects=/tmp/redirects.json \
--roots=site/src \
--out=/tmp/audit-report.md 2>&1 | tee /tmp/audit-output.txt
count=$(grep -oP 'Total findings: \K\d+' /tmp/audit-output.txt || echo "0")
if [ "$count" -gt 0 ]; then
echo "::error::Found $count stale docs path(s) pointing at redirect sources"
cat /tmp/audit-report.md >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
- name: Send Slack notification
if: failure() && github.event_name != 'pull_request'
run: |
curl \
-X POST \
-H 'Content-type: application/json' \
-d '{"text":":warning: *Stale docs paths found in site/src/.*\nTS/TSX files reference docs URLs that now redirect. Please check the logs: '"${LOGS_URL}"'"}' "${{ secrets.DOCS_LINK_SLACK_WEBHOOK }}"
echo "Sent Slack notification"
env:
LOGS_URL: https://github.com/coder/coder/actions/runs/${{ github.run_id }}