From 9bdbf5c5900cbd49981a53c964d1c0e036583ed5 Mon Sep 17 00:00:00 2001 From: Shunsuke KITADA Date: Sun, 12 Jul 2026 13:35:08 +0900 Subject: [PATCH 1/4] ci: gate deploy on link check, add pytest workflow, and harden permissions - gh-pages.yml: deploy now depends on check-broken-links too, so a broken-link failure blocks the gh-pages publish instead of racing it. - gh-pages.yml: add top-level permissions: contents: read (deploy uses a deploy_key secret, not GITHUB_TOKEN, so no write scope is needed). - gh-pages.yml: move the push/workflow_dispatch + main-branch guard from the Deploy step's if: to the deploy job's if:, so PR runs skip the whole job instead of downloading the artifact just to no-op. - Bump lychee from v0.21.0 to v0.24.2 in gh-pages.yml and lychee-prune.yml, and pin mise.toml's lychee tool to the same version. - Add .github/workflows/pytest.yml to run the tests/ suite via 'uv run --with pytest --with ruamel.yaml pytest tests/ -q' on PRs and pushes to main, scoped to tests/**, scripts/**, .agents/skills/**, and the workflow file itself. - config/_default/hugo.yaml: change timeout from bare 600000 to "600s". Hugo v0.136.5 parses the bare number as seconds, so the effective timeout was ~166 hours; "600s" restores the original 10-minute intent (600000 was a leftover from Hugo's old milliseconds interpretation). Co-Authored-By: Claude Fable 5 --- .github/workflows/gh-pages.yml | 9 +++++--- .github/workflows/lychee-prune.yml | 2 +- .github/workflows/pytest.yml | 33 ++++++++++++++++++++++++++++++ config/_default/hugo.yaml | 2 +- mise.toml | 2 +- 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8f8649b9de..9a488849a1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -9,6 +9,9 @@ on: branches: - main +permissions: + contents: read + jobs: check-typo: runs-on: ubuntu-latest @@ -68,13 +71,14 @@ jobs: uses: lycheeverse/lychee-action@v2 with: fail: true - lycheeVersion: v0.21.0 + lycheeVersion: v0.24.2 # --root-dir is required to resolve root-relative links (e.g. /talk/...) # in the built HTML; without it lychee silently skips them. args: "${{ vars.ARTIFACT_PATH }} --root-dir ${{ github.workspace }}/${{ vars.ARTIFACT_PATH }} --config .lychee/config.toml --exclude-file .lychee/exclude-temporary.txt --exclude-file .lychee/exclude-permanent.txt" deploy: - needs: build + needs: [build, check-broken-links] + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: @@ -86,7 +90,6 @@ jobs: - name: Deploy uses: peaceiris/actions-gh-pages@v4 - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} publish_dir: ${{ vars.ARTIFACT_PATH }} diff --git a/.github/workflows/lychee-prune.yml b/.github/workflows/lychee-prune.yml index 10ae493f82..7c1df85e32 100644 --- a/.github/workflows/lychee-prune.yml +++ b/.github/workflows/lychee-prune.yml @@ -89,7 +89,7 @@ jobs: uses: lycheeverse/lychee-action@v2 with: fail: false - lycheeVersion: v0.21.0 + lycheeVersion: v0.24.2 format: json output: lychee-excludes.json args: "--config .lychee/config.toml lychee-input.txt" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000000..6f371ef2ae --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,33 @@ +name: Pytest + +on: + push: + branches: + - main + paths: + - "tests/**" + - "scripts/**" + - ".agents/skills/**" + - ".github/workflows/pytest.yml" + pull_request: + paths: + - "tests/**" + - "scripts/**" + - ".agents/skills/**" + - ".github/workflows/pytest.yml" + +permissions: + contents: read + +jobs: + pytest: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - name: Setup uv + uses: astral-sh/setup-uv@v8.3.2 + + - name: Run tests + run: uv run --with pytest --with ruamel.yaml pytest tests/ -q diff --git a/config/_default/hugo.yaml b/config/_default/hugo.yaml index 8fa5b5c1cb..163eb19834 100644 --- a/config/_default/hugo.yaml +++ b/config/_default/hugo.yaml @@ -40,7 +40,7 @@ imaging: resampleFilter: Lanczos quality: 75 anchor: Smart -timeout: 600000 +timeout: "600s" taxonomies: tag: tags category: categories diff --git a/mise.toml b/mise.toml index bc91dcebb8..f2c73d481f 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,5 @@ [tools] hugo-extended = "0.136.5" -lychee = "latest" +lychee = "0.24.2" "go:github.com/shunk031/tcardgen" = "latest" From c5500e6d44a20d8e957d3875a9bb855117615cb4 Mon Sep 17 00:00:00 2001 From: Shunsuke KITADA Date: Sun, 12 Jul 2026 14:03:20 +0900 Subject: [PATCH 2/4] ci: address review feedback on lychee version source and workflow naming - Read the lychee version from mise.toml in gh-pages.yml and lychee-prune.yml instead of hardcoding lycheeVersion in each workflow, so mise.toml is the single source of truth and the version is no longer managed in three places. In lychee-prune.yml the version is read right after checkout, before the prune-branch step switches branches. - Rename the pytest workflow to a tool-agnostic name: pytest.yml -> test.yml, workflow name Pytest -> Test, job pytest -> test, and update the self-referencing paths filter accordingly. Co-Authored-By: Claude Fable 5 --- .github/workflows/gh-pages.yml | 9 ++++++++- .github/workflows/lychee-prune.yml | 10 +++++++++- .github/workflows/{pytest.yml => test.yml} | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) rename .github/workflows/{pytest.yml => test.yml} (82%) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9a488849a1..b3efb5fdfb 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -60,6 +60,13 @@ jobs: # Checkout the repository to access the lychee config file - uses: actions/checkout@v7 + # Keep mise.toml as the single source of truth for the lychee version + - name: Read lychee version from mise.toml + id: lychee-version + run: | + version="$(python3 -c 'import tomllib; print(tomllib.load(open("mise.toml", "rb"))["tools"]["lychee"])')" + echo "version=v${version}" >> "$GITHUB_OUTPUT" + - name: Download Build Artifact uses: actions/download-artifact@v8 with: @@ -71,7 +78,7 @@ jobs: uses: lycheeverse/lychee-action@v2 with: fail: true - lycheeVersion: v0.24.2 + lycheeVersion: ${{ steps.lychee-version.outputs.version }} # --root-dir is required to resolve root-relative links (e.g. /talk/...) # in the built HTML; without it lychee silently skips them. args: "${{ vars.ARTIFACT_PATH }} --root-dir ${{ github.workspace }}/${{ vars.ARTIFACT_PATH }} --config .lychee/config.toml --exclude-file .lychee/exclude-temporary.txt --exclude-file .lychee/exclude-permanent.txt" diff --git a/.github/workflows/lychee-prune.yml b/.github/workflows/lychee-prune.yml index 7c1df85e32..8ad4a51a55 100644 --- a/.github/workflows/lychee-prune.yml +++ b/.github/workflows/lychee-prune.yml @@ -43,6 +43,14 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} + # Keep mise.toml as the single source of truth for the lychee version. + # Read it here, before "Prepare prune branch" switches branches. + - name: Read lychee version from mise.toml + id: lychee-version + run: | + version="$(python3 -c 'import tomllib; print(tomllib.load(open("mise.toml", "rb"))["tools"]["lychee"])')" + echo "version=v${version}" >> "$GITHUB_OUTPUT" + - name: Download Build Artifact uses: actions/download-artifact@v8 with: @@ -89,7 +97,7 @@ jobs: uses: lycheeverse/lychee-action@v2 with: fail: false - lycheeVersion: v0.24.2 + lycheeVersion: ${{ steps.lychee-version.outputs.version }} format: json output: lychee-excludes.json args: "--config .lychee/config.toml lychee-input.txt" diff --git a/.github/workflows/pytest.yml b/.github/workflows/test.yml similarity index 82% rename from .github/workflows/pytest.yml rename to .github/workflows/test.yml index 6f371ef2ae..672cbddb0c 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Pytest +name: Test on: push: @@ -8,19 +8,19 @@ on: - "tests/**" - "scripts/**" - ".agents/skills/**" - - ".github/workflows/pytest.yml" + - ".github/workflows/test.yml" pull_request: paths: - "tests/**" - "scripts/**" - ".agents/skills/**" - - ".github/workflows/pytest.yml" + - ".github/workflows/test.yml" permissions: contents: read jobs: - pytest: + test: runs-on: ubuntu-latest steps: From 8ef66fb4b0ad1de47875315f28bc0da2146a6cf9 Mon Sep 17 00:00:00 2001 From: Shunsuke KITADA Date: Sun, 12 Jul 2026 14:20:05 +0900 Subject: [PATCH 3/4] ci: pass --root-dir to lychee for root-relative link resolution lychee v0.24 errors out on root-relative links (e.g. /tags/...) in local files unless --root-dir is given, whereas v0.21 silently skipped them, so the v0.24.2 bump broke the check-broken-links job. Point --root-dir at the built artifact directory, matching the approach in PR #377. Co-Authored-By: Claude Fable 5 --- .github/workflows/gh-pages.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index b3efb5fdfb..cf88becacc 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -79,8 +79,9 @@ jobs: with: fail: true lycheeVersion: ${{ steps.lychee-version.outputs.version }} - # --root-dir is required to resolve root-relative links (e.g. /talk/...) - # in the built HTML; without it lychee silently skips them. + # --root-dir is required since lychee v0.24 to resolve root-relative + # links (e.g. /talk/...) in the built HTML; without it lychee errors + # out instead of silently skipping them like v0.21 did. args: "${{ vars.ARTIFACT_PATH }} --root-dir ${{ github.workspace }}/${{ vars.ARTIFACT_PATH }} --config .lychee/config.toml --exclude-file .lychee/exclude-temporary.txt --exclude-file .lychee/exclude-permanent.txt" deploy: From bd2008c7c69dc4a92c37e076a8451ee45f61a7b8 Mon Sep 17 00:00:00 2001 From: Shunsuke KITADA Date: Sun, 12 Jul 2026 14:24:46 +0900 Subject: [PATCH 4/4] fix(links): exclude book.impress.co.jp from link check lychee >= v0.24 uses rustls, which cannot complete a TLS handshake with book.impress.co.jp because the server only offers legacy TLS cipher suites, even though the page is alive (curl returns 200). Add it to the temporary excludes so the weekly prune workflow re-checks it and removes the entry once the handshake succeeds. Co-Authored-By: Claude Fable 5 --- .lychee/exclude-temporary.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.lychee/exclude-temporary.txt b/.lychee/exclude-temporary.txt index 1d461767e5..2d78efcf97 100644 --- a/.lychee/exclude-temporary.txt +++ b/.lychee/exclude-temporary.txt @@ -23,3 +23,8 @@ confit.atlas.jp www.scimagojr.com clustrmaps.com www.meethawaii.com/convention-center/ + +# The page is alive (curl returns 200) but lychee >= v0.24 (rustls) cannot +# complete a TLS handshake because the server only offers legacy TLS cipher +# suites. Re-check regularly in case the server's TLS config is upgraded. +book.impress.co.jp