From 3a7ec8eeb0cc3244aef0b6902847506ef9126952 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 30 Jul 2026 10:58:56 -0400 Subject: [PATCH 1/2] Make CI check what it claims to check Four fixes, each for something currently unguarded. 1. YAML schema validation never validated anything. The step ran three commands that all pointed at paths which do not exist: npx yaml-ls-check .\articles The backslashes are Windows separators. On the Ubuntu runner bash treats `\a` as an escape, so the argument reaching the tool is `.articles`, and the output was `Validating 0 YAML files. Validation complete.` with exit 0 on every run. Correcting the paths alone would not have been enough. yaml-ls-check reads its schema mapping from `/.vscode/settings.json`, where `` is the directory passed on the command line, so `yaml-ls-check articles` looks for `articles/.vscode/settings.json`, finds nothing, and falls back to syntax-only checking. Relative roots also break the mapping's `./schema/...` references, which resolve to `/schema/...`. Passing the workspace as an absolute path is the form that works: the schemas load and violations are reported. Against the tree as it stands this validates 11 files, all passing. The separate `npm install yaml-ls-check` step is gone. It installed the entire project dependency tree as a side effect and wrote to package.json and package-lock.json in CI; `npx --yes` fetches just the tool. 2. Lint never ran anywhere. `next build` does not lint, and no workflow invoked `npm run lint`, so an ESLint or TypeScript upgrade could go green without its effect on the lint rules ever being exercised. 3. No job had a timeout. A stalled step therefore ran against GitHub's 6-hour default before failing. This is not hypothetical: a `Validate YAML files` step recently sat over six minutes on work that normally takes 24 seconds. The deployment workflow is the more serious case, since its `pages` concurrency group means one stuck run blocks every deployment queued behind it. 4. Nothing checked where the lockfile resolves from. A lockfile generated behind a corporate registry proxy records that proxy's backing-feed URLs instead of registry.npmjs.org. Those hosts are unreachable from public CI and by outside contributors, and the failure appears far from its cause. The new step asserts every `resolved` URL points at the public registry and prints the offenders when it fails. Also adds a concurrency group so a rebase or force-push supersedes in-flight pull request runs rather than leaving several racing on stale commits. Pushes to main are never cancelled, so every default-branch commit keeps a recorded result. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WWCBtvyCpxc2aqtyLDhDeE --- .github/workflows/continuous-deployment.yml | 5 +++ .github/workflows/continuous-integration.yml | 43 ++++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 18e37af..62bc4c3 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -20,6 +20,10 @@ jobs: permissions: contents: read runs-on: ubuntu-22.04 + # Without an explicit timeout a stalled step runs against GitHub's 6-hour + # default before failing, which for `pages` concurrency means blocking + # every deployment queued behind it. + timeout-minutes: 30 steps: - name: Checkout source uses: actions/checkout@v7 @@ -132,6 +136,7 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + timeout-minutes: 10 needs: - build # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1fb2e27..38d919e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -9,11 +9,19 @@ on: pull_request: branches: - main +# Supersede in-flight runs for a pull request when new commits arrive, so a +# rebase or force-push does not leave several runs racing on stale commits. +# Pushes to main are never cancelled: every commit on the default branch +# should get a recorded result. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: # YAML schema validation job yaml-schema-validation: name: Validate YAML files against schemas runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v7 @@ -21,18 +29,19 @@ jobs: uses: actions/setup-node@v7 with: node-version: 24 - - name: Install dependencies - run: | - npm install yaml-ls-check - name: Validate YAML files - run: | - npx yaml-ls-check .\articles - npx yaml-ls-check .\blogs - npx yaml-ls-check .\reference + # Pass the repository root as an absolute path. yaml-ls-check reads its + # schema mapping from `/.vscode/settings.json`, so the root it is + # given has to be the directory holding `.vscode`. Passing a + # subdirectory silently skips schema validation and checks only YAML + # syntax; passing a relative path breaks the mapping's `./schema/` + # references. + run: npx --yes yaml-ls-check "$GITHUB_WORKSPACE" # Build validation job build-validation: name: Validate Next.js build runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 @@ -47,8 +56,28 @@ jobs: with: ruby-version: 3.3 bundler-cache: true + - name: Check lockfile resolves against the public registry + # A lockfile generated behind a registry proxy records that proxy's + # backing-feed URLs, which public CI and outside contributors cannot + # fetch. Catch it here rather than after it is merged. + run: | + node -e " + const lock = require('./package-lock.json'); + const bad = Object.entries(lock.packages) + .filter(([, v]) => v.resolved && !v.resolved.startsWith('https://registry.npmjs.org/')); + if (bad.length) { + console.error('package-lock.json has ' + bad.length + ' resolved URL(s) outside registry.npmjs.org:'); + bad.slice(0, 10).forEach(([name, v]) => console.error(' ' + name + ' -> ' + v.resolved)); + process.exit(1); + } + console.log('All resolved URLs point at registry.npmjs.org.'); + " - name: Install dependencies run: npm ci + - name: Lint + # `next build` does not lint, so without this an ESLint or TypeScript + # upgrade can land without its effect on the lint rules ever running. + run: npm run lint - name: Build Next.js site # Validate that the site builds successfully # From bc05837f1cec83035bb557d8eb88465d585de217 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 30 Jul 2026 11:14:11 -0400 Subject: [PATCH 2/2] Exclude vendored Jekyll gems from lint The new lint step failed on third-party JavaScript, not ours: vendor/bundle/ruby/3.3.0/gems/jekyll-4.4.1/lib/jekyll/commands/serve/ livereload_assets/livereload.js 1:221 error Missing semicolon semi ruby/setup-ruby runs with bundler-cache: true, which vendors gems into vendor/bundle before the lint step, so ESLint sees Jekyll's minified livereload.js. The directory is already in .gitignore, but ESLint flat config does not read .gitignore, so the ignore has to be repeated in eslint.config.mjs. This is invisible locally, where vendor/ does not exist. Also ignores .jekyll-cache and any _site output for the same reason. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WWCBtvyCpxc2aqtyLDhDeE --- eslint.config.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 355a810..b806de5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,13 @@ export default defineConfig([ "coverage/**", "build/**", "node_modules/**", + // Jekyll vendors its gems into vendor/bundle when bundler-cache is on, + // which drops third-party JavaScript such as Jekyll's minified + // livereload.js into the tree. These paths are in .gitignore, but flat + // config does not read .gitignore, so they have to be listed here too. + "vendor/**", + ".jekyll-cache/**", + "**/_site/**", ], }, {