fix(ci): R3-2 - correctness + cost pass on the CI pipeline - #423
Conversation
Correctness: - hugo_stats.json gets its own EXACT-key-only cache (no restore-keys): a partial hit restored a stale stats file from an older tree, and the warm-up skip trusted it - PurgeCSS could purge classes live in the current tree (the 2026-07-19 .sr-only incident class, re-opened via cache). Exact hit = identical tree = valid stats - test.yml PR runs check out the MERGE commit (default checkout); head_ref tested the tip without the base and breaks fork PRs. Record dispatches still check out the branch ref (needed for the push) - record mode runs rake test:system (not the full suite - a unit failure used to abort AFTER 15+ min of recording), and the commit step is always()-gated + porcelain-guarded so recorded PNGs land even when one page's test is red - visual + link-check paths filters gain root layouts/** and assets/** (root-level overrides skipped both gates) Cost: - sync fan-out gated: publish.yml and link-check.yml workflow_run jobs skip when the sync pushed nothing (head_sha == current master head) - 'Sync articles' fires every 10 min 8-21 UTC and mostly commits nothing; that was ~84 no-op deploy+test cascades per day - _dest dropped from the setup-hugo cache (~1-2 GB per job, evicting every other cache from the 10 GB quota; the build regenerates it) - link-check builds ONCE: composite runs with build:'false', rake test:links does its own production build - the double build blew the 10-minute timeout on cold caches (seen live on PR #422) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXHeUErqoiyH9xN1mjC8cH
📝 WalkthroughWalkthroughThe PR separates Hugo caches, gates workflow fan-out, adjusts link and screenshot test triggers, preserves merge-commit testing, and makes screenshot baseline recording resilient to test failures and unchanged fixtures. ChangesCI correctness
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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. Comment |
|
@coderabbitai review Generated by Claude Code |
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/setup-hugo/action.yml:
- Around line 64-68: Update the hugo_stats.json cache key in the setup workflow
to include a token derived from BUILD_DRAFTS, distinguishing draft and
production cache entries. Update the related visual-cache documentation to
describe the same draft-scoped cache behavior.
In @.github/workflows/publish.yml:
- Around line 36-41: Replace the workflow_run head_sha comparison in the publish
workflow’s deploy and unit_tests jobs with an explicit signal that Sync articles
pushed a commit, or trigger this workflow directly from the sync push. Apply the
same gating rule to the corresponding runs in link-check.yml, preserving
execution for normal non-workflow_run triggers.
In @.github/workflows/test.yml:
- Around line 119-124: Update the “Commit updated baselines” step condition to
allow completed successful or failed recording steps while explicitly excluding
a cancelled steps.record outcome. Preserve the existing always(),
workflow_dispatch, and inputs.update-baselines guards.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c3ae290c-226d-4ea8-9c6d-6f6cc417d643
📒 Files selected for processing (6)
.github/actions/setup-hugo/action.yml.github/workflows/link-check.yml.github/workflows/publish.yml.github/workflows/test.yml.okf/build/ci-gates.md.okf/log.md
| - name: Cache hugo_stats.json (exact tree match only) | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: hugo_stats.json | ||
| key: ${{ runner.os }}-hugo-stats-${{ inputs.environment }}-${{ hashFiles('config/**', 'postcss.config.js', 'package.json', '**/bun.lockb', 'themes/**', 'layouts/**', 'content/**') }} |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 8 'setup-hugo|hugo-version:|BUILD_DRAFTS:|hugo\.toml|build-drafts' .github || true
fd -t f -a 'hugo\.(toml|yaml|yml|json)$' .Repository: jetthoughts/jetthoughts.github.io
Length of output: 10301
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== setup-hugo action =="
cat -n .github/actions/setup-hugo/action.yml | sed -n '1,110p'
echo "== test workflow setup-hugo caller and bin/hugo-build references =="
cat -n .github/workflows/test.yml | sed -n '70,95p'
echo "== bin/hugo-build draft/env handling =="
fd -t f -a 'hugo-build|hugo\.toml' . | sed 's#^\./##'
if [ -f bin/hugo-build ]; then
cat -n bin/hugo-build | sed -n '1,220p'
fi
echo "== relevant docs gate references =="
fd -t f -a 'ci-gates\.md' .
if [ -f .okf/build/ci-gates.md ]; then
cat -n .okf/build/ci-gates.md | sed -n '1,120p'
fi
echo "== search for buildDrafts / BUILD_DRAFTS / hugo_stats =="
rg -n -C 4 'BUILD_DRAFTS|buildDrafts|hugo_stats\.json|bin/hugo-build/.github/actions/setup-hugo' .Repository: jetthoughts/jetthoughts.github.io
Length of output: 50391
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== bin/qtest outline/relevant refs =="
ast-grep outline bin/qtest --view expanded || true
rg -n -C 5 'hugo_stats|BUILD_DRAFTS|setup-hugo|qtest|bin/hugo-build|hugo build' bin/qtest bin/setup-test-env test || true
if [ -f bin/qtest ]; then
cat -n bin/qtest | sed -n '1,220p'
fi
echo "== root config and theme draft configs =="
for f in hugo.toml config/_default/hugo.toml config/development/hugo.toml; do
[ -f "$f" ] && { echo "--- $f"; cat -n "$f" | sed -n '1,90p'; }
done
echo "== theme-wide buildDrafts settings =="
rg -n 'buildDrafts|drafts|enabled = false|writeStats' --glob '*.toml' --glob '*.hbs' --glob '*.html' themes config content bin .github || trueRepository: jetthoughts/jetthoughts.github.io
Length of output: 28945
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== bin/build-if-stale and env/build details =="
cat -n bin/build-if-stale | sed -n '1,220p'
echo "== test workflow cache/build sequence around setup-hugo =="
cat -n .github/workflows/test.yml | sed -n '1,150p'
echo "== static cache-key model: does key distinguish draft env?"
python3 - <<'PY'
import re
line='key: ${{ runner.os }}-hugo-stats-${{ inputs.environment }}-${{ hashFiles(\'config/**\', \'postcss.config.js\', \'package.json\', \'**/bun.lockb\', \'themes/**\', \'layouts/**\', \'content/**\') }}'
print(line.strip())
print("BUILD_DRAFTS appears in key:", "BUILD_DRAFTS" in line or "buildDrafts" in line or "build-drafts" in line)
print("hugo-version appears in key:", "inputs.hugo-version" in line)
print("root hugo.toml in hashFiles:", bool(re.search(r"hashFiles\([^)]*'hugo\.toml'", line)))
print("buildDrafts env used by Hugo output:", open("bin/hugo-build").read().count("BUILD_DRAFTS"))
PY
echo "== content draft post that requires BUILD_DRAFTS =="
rg -n -C 4 'draft\s*:\s*true|draft: true|codeblock-styles-fixture' config content archives themes --glob '*.md' --glob '*.json' --glob '*.toml'Repository: jetthoughts/jetthoughts.github.io
Length of output: 11300
Key hugo_stats.json by draft scope too.
The current key does not include BUILD_DRAFTS, but test builds run Hugo with --buildDrafts. If a production warm tree cache restores a stats file, the draft fixture can be served without regenerating it, which may make PurgeCSS miss draft-only classes. Add a draft-mode/cache-mode token to the key and make the visual-cache documentation match that scope.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/setup-hugo/action.yml around lines 64 - 68, Update the
hugo_stats.json cache key in the setup workflow to include a token derived from
BUILD_DRAFTS, distinguishing draft and production cache entries. Update the
related visual-cache documentation to describe the same draft-scoped cache
behavior.
| if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_sha != github.sha) }} | ||
| uses: ./.github/workflows/_hugo.yml | ||
|
|
||
| unit_tests: | ||
| name: Unit Tests | ||
| if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_sha != github.sha) }} |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 12 \
'workflow_run|Sync articles|push:|workflows:|git push|git commit' \
.github/workflowsRepository: jetthoughts/jetthoughts.github.io
Length of output: 13609
🌐 Web query:
GitHub Actions workflow_run event github.event.workflow_run.head_sha current branch HEAD
💡 Result:
When using the workflow_run event in GitHub Actions, the workflow executes in the context of the repository's default branch, not the branch that triggered the original workflow [1][2]. Therefore, standard environment variables like GITHUB_REF or GITHUB_SHA will reflect the default branch rather than the branch of the triggering commit [2][3]. To access information about the original workflow run that triggered the current event, you must use the github.event.workflow_run context [2]. Key points for accessing branch and commit information: 1. Accessing the Branch Name: You can obtain the name of the branch that triggered the original workflow by using ${{ github.event.workflow_run.head_branch }} [2][3]. 2. Accessing the Commit SHA: You can obtain the SHA of the commit that triggered the original workflow by using ${{ github.event.workflow_run.head_sha }} [4][5]. 3. Checking Out the Correct Code: Because the workflow runs on the default branch by default, you must explicitly configure your checkout step to use the original branch or SHA if you need to access the code from that specific commit [2][3]. Example usage with actions/checkout: - uses: actions/checkout@v4 with: ref: ${{ github.event.workflow_run.head_sha }} Using head_sha is generally safer than head_branch for checkout, as it ensures you are working with the exact state of the code that triggered the original workflow, preventing issues if new commits have been pushed to that branch in the interim [5].
Citations:
- 1: workflow_run trigger filters wrong branch following a previous workflow_run event actions/runner#1628
- 2: https://github.com/potherca-blog/github-actions-workflow_run-event
- 3: https://stackoverflow.com/questions/67138255/github-actions-read-branch-changes-on-workflow-run
- 4: https://stackoverflow.com/questions/61393981/getting-base-branch-sha-on-pull-request-in-github-action-workflow
- 5: https://github.com/octokit/webhooks/blob/main/payload-types/schema.d.ts
🌐 Web query:
GitHub workflow_run event github.sha head_sha head_commit
💡 Result:
In a GitHub Actions workflow triggered by the workflow_run event, the standard github.sha context does not represent the commit that triggered the original workflow [1]. Instead, github.sha defaults to the last commit on the default branch of the repository where the workflow is running [2][3]. To access the SHA of the commit that triggered the original workflow, you must use the event payload available within the github.event context [4][5]. Specifically, you should use: ${{ github.event.workflow_run.head_sha }} This field, head_sha, contains the SHA of the commit that triggered the workflow run that this workflow is reacting to [4][1]. It is essential to use this specific property because the workflow_run event context differs significantly from events like push, where github.sha directly points to the triggering commit [4][5]. Note that head_commit is also present in the webhook payload for some events (like push), but for workflow_run, the head_sha within the workflow_run object is the standard and correct way to retrieve the triggering commit's identifier [4][6].
Citations:
- 1: https://github.com/prompt/actions-commit-hash
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 3: https://raw.githubusercontent.com/github/docs/refs/heads/main/content/actions/reference/workflows-and-actions/events-that-trigger-workflows.md
- 4: Document how to find the equivalent of
github.shain other events thanpushgithub/docs#30093 - 5: https://github.com/github/docs/blob/main/content/actions/reference/workflows-and-actions/contexts.md
- 6: https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=resolve
Gate on whether Sync articles pushed, not on any default-branch movement.
github.event.workflow_run.head_sha is the commit that the Sync articles run was based on, while github.sha is the current default-branch head in the workflow context. If an unrelated commit lands after the sync starts, the condition passes even when the sync performed no commit-push. Move deploy/test/link-check runs behind an explicit pushed-result signal, or start this workflow from the sync’s push, and apply the same rule in .github/workflows/link-check.yml:24-53.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/publish.yml around lines 36 - 41, Replace the workflow_run
head_sha comparison in the publish workflow’s deploy and unit_tests jobs with an
explicit signal that Sync articles pushed a commit, or trigger this workflow
directly from the sync push. Apply the same gating rule to the corresponding
runs in link-check.yml, preserving execution for normal non-workflow_run
triggers.
| # always(): freshly recorded baselines must be committed even when a | ||
| # test failed mid-run (a red test does not invalidate the OTHER pages' | ||
| # recordings). Gated on the record step actually having run, and the | ||
| # commit is a no-op when nothing changed. | ||
| - name: Commit updated baselines | ||
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.update-baselines }} | ||
| if: ${{ always() && steps.record.outcome != 'skipped' && github.event_name == 'workflow_dispatch' && inputs.update-baselines }} |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate test.yml =="
fd -a 'test\.yml$' . | sed 's#^\./##' || true
echo "== relevant workflow excerpt =="
if [ -f .github/workflows/test.yml ]; then
nl -ba .github/workflows/test.yml | sed -n '90,140p'
fi
echo "== workflow_dispatch inputs/steps references =="
if [ -f .github/workflows/test.yml ]; then
rg -n "update-baselines|steps\.record|record:|cancelled\(\)|pullRequest|system|commit updated baselines" .github/workflows/test.yml
fi
echo "== GitHub Actions cancel/always/outcome docs probe =="
python3 - <<'PY'
# Read-only semantic probe: demonstrate the workflow expression logic
# from the referenced condition without importing repository code.
import json, pathlib
p = pathlib.Path('.github/workflows/test.yml')
print('exists', p.exists())
if p.exists():
text = p.read_text()
found = False
for i,line in enumerate(text.splitlines(),1):
if 'if: ${{ always() && steps.record.outcome !=' in line:
found = True
print(f'line {i}: {line.strip()}')
print('contains cancelled guard:', '!cancelled()' in line)
print('contains skipped guard:', '!= '\''skipped'\'' in line or "!= '\''skipped'\''" in line')
if not found:
print('condition not found')
PYRepository: jetthoughts/jetthoughts.github.io
Length of output: 311
🌐 Web query:
GitHub Actions contexts outcome cancelled status cancellation status always() cancelled()
💡 Result:
In GitHub Actions, the cancelled() function is a status check function used within the if conditional to determine if the workflow run or a specific job has been cancelled [1]. Key concepts and behaviors: 1. cancelled function: When used in an if conditional, cancelled() returns true if the workflow run or job was cancelled [1]. This is commonly used to execute cleanup steps or report status after a cancellation [1]. 2. always function: The always() function is used in if conditionals to ensure that a step or job runs regardless of the outcome of previous steps or the status of the job [2][1]. A step with if: always() will execute even if the workflow was cancelled or a previous step failed [1]. 3. Interaction with Cancellation: When a workflow is cancelled, GitHub Actions re-evaluates if conditions for currently running jobs and steps [1]. Jobs or steps without an explicit if condition effectively have a default if: success() condition, meaning they will not run if the workflow is cancelled [1]. By using if: always(), you can override this default behavior to ensure specific tasks continue to run during cancellation [1]. 4. Contextual Notes: - Job Status: The job.status context can have a value of cancelled [2]. - Limitations: In some complex scenarios, such as certain matrix configurations involving reusable workflows, cancelled() might not return true as expected even when the job is cancelled [3]. In such cases, checking the result context of dependencies (e.g., contains(needs.*.result, 'cancelled')) is often used as a workaround [3]. In summary, use if: always() to ensure a step runs despite cancellation, and use if: cancelled() to trigger a step specifically when a cancellation has occurred [1].
Citations:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-cancellation
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts
- 3:
cancelled()returnsfalseafter workflow cancel actions/runner#3041
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant workflow excerpt =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/test.yml')
if not p.exists():
print('missing')
raise SystemExit
for i,line in enumerate(p.read_text().splitlines(), 1):
if 90 <= i <= 140:
print(f'{i:6d}: {line}')
PY
echo "== step name references and cancellation guard =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/test.yml')
for i,line in enumerate(p.read_text().splitlines(), 1):
if any(tok in line for tok in ['update-baselines', 'steps.record.outcome', 'cancelled()', 'record:']):
print(f'{i}: {line.strip()}')
PY
echo "== check installed GitHub CLI availability/permissions =="
command -v gh || true
if command -v gh >/dev/null 2>&1; then
env | grep -i gh || true
gh --version 2>&1 | head -n 5 || true
fiRepository: jetthoughts/jetthoughts.github.io
Length of output: 4722
🌐 Web query:
GitHub Actions workflow_context cancelled job_status cancelled step outcome cancelled documentation
💡 Result:
In GitHub Actions, the term cancelled is used to describe the status or conclusion of a job or step after a workflow has been manually cancelled or terminated by the system [1][2]. Contexts and Terminology 1. Job Status and Conclusion The status of a job (e.g., job.status) indicates its current lifecycle phase, such as queued, in_progress, or completed [3]. When a job finishes, it receives a conclusion, which describes the final result [3][4]. A job can have a conclusion of cancelled when the workflow run is cancelled before the job completes [3][2]. 2. Step Outcome and Conclusion Each step in a job has an outcome and a conclusion [1][2]: - outcome: The result of the step execution before the continue-on-error setting is applied [1][2]. - conclusion: The final result of the step after the continue-on-error setting is applied [1][2]. If a workflow is cancelled while a step is running, both the outcome and conclusion of that step will typically be marked as cancelled [1][2]. Expression Usage You can use the cancelled expression in if conditional statements to perform specific actions only when a job or step has been cancelled [5][2]. For example: - if: cancelled can be used on jobs or steps to ensure they run even when the workflow has been cancelled [5]. Important Note The term workflow_context is not a standard, top-level context in GitHub Actions expressions; documentation refers to the github context or specific step/job contexts [1][2]. When a workflow is cancelled, the system re-evaluates if conditions, sends termination signals to runners, and eventually forces termination of jobs and steps that do not exit promptly [5].
Citations:
- 1: https://github.com/github/docs/blob/main/content/actions/reference/workflows-and-actions/contexts.md
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts
- 3: https://docs.github.com/en/rest/actions/workflow-jobs?apiVersion=2022-11-28
- 4: https://docs.github.com/en/pull-requests/reference/status-checks
- 5: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-cancellation
🌐 Web query:
GitHub Actions always cancelled() cancelled step condition example job status cancelled
💡 Result:
In GitHub Actions, the cancelled() status check function is used to run a job or step only if the workflow run has been cancelled [1]. By default, jobs and steps are configured with an implicit success() condition, meaning they are skipped if the workflow is cancelled [1][2]. To ensure a step or job runs even when the workflow is cancelled, you must explicitly use the cancelled() function in your if condition [1][2]. Example: Running a Step on Cancellation You can use if: cancelled() to trigger cleanup or notification steps specifically when a user cancels the workflow [1]. jobs: cleanup-job: runs-on: ubuntu-latest if: cancelled steps: - name: Run on cancellation run: echo "The workflow was cancelled. Performing cleanup." Key Considerations and Limitations: 1. Cancellation Logic: When a workflow is cancelled, GitHub re-evaluates if conditions for all running jobs and steps [2]. Jobs or steps with if: always() or if: cancelled() will continue to execute, while others are marked for cancellation [2]. 2. Implicit Success: If you do not specify an if condition, GitHub defaults to if: success() [1][2]. This is why steps are typically skipped upon cancellation. 3. Job-Level Cancellation: If you use if: always() on a job, it will run regardless of the workflow's status (success, failure, or cancellation) [1]. If you want a job to run on cancellation but NOT on failure, you must explicitly combine conditions, such as if: cancelled() || success() [1]. 4. Composite Actions and Complex Cases: There have been known issues where cancelled() may not behave as expected in certain complex scenarios, such as within specific composite actions or when dependent jobs are skipped [3][4][5]. In some cases, checking contains(needs.*.result, 'cancelled') at the job level provides a more reliable alternative for monitoring the status of upstream dependencies [5]. 5. Termination: After a cancellation is triggered, there is a 5-minute timeout period. If the job or step is still running after this period, the runner will forcibly terminate the process [2][6].
Citations:
- 1: https://timesofcloud.com/github-actions/status-functions/
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-cancellation
- 3: Fixes
if:cancelled()composite steps not running and normal composite steps not interrupting when the job is cancelled. actions/runner#2638 - 4: https://stackoverflow.com/questions/75754198/custom-github-action-if-cancelled-would-not-run-when-workflow-is-cancelled
- 5:
cancelled()returnsfalseafter workflow cancel actions/runner#3041 - 6: https://docs.github.com/en/enterprise-server@3.19/actions/reference/workflows-and-actions/workflow-cancellation
🌐 Web query:
site:docs.github.com steps *.outcome cancelled action
💡 Result:
In GitHub Actions, steps.<step_id>.outcome and steps.<step_id>.conclusion are properties within the steps context that indicate the result of a completed step [1]. The outcome property represents the result of a completed step before any continue-on-error setting is applied [1]. Possible values for both outcome and conclusion include success, failure, cancelled, or skipped [1]. The distinction between the two is specifically relevant when continue-on-error is used: - If a step fails and continue-on-error is set to true, the outcome will be failure, but the final conclusion will be success [1]. If a step is cancelled, the outcome will be reported as cancelled [1]. The steps context is only available for steps that have an id specified and have already run [1].
Citations:
Do not commit baselines after cancellation.
Line [124] blocks only steps.record.outcome == 'skipped'. A canned test:system run can leave partial baseline rewrites; the commit step can then push them because always() remains true and steps.record.outcome can be cancelled. Keep the commit for successful and failed recordings, but block it when cancelled.
Guard the commit step against cancellation
- if: ${{ always() && steps.record.outcome != 'skipped' && github.event_name == 'workflow_dispatch' && inputs.update-baselines }}
+ if: ${{ always() && !cancelled() && steps.record.outcome != 'skipped' && github.event_name == 'workflow_dispatch' && inputs.update-baselines }}📝 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.
| # always(): freshly recorded baselines must be committed even when a | |
| # test failed mid-run (a red test does not invalidate the OTHER pages' | |
| # recordings). Gated on the record step actually having run, and the | |
| # commit is a no-op when nothing changed. | |
| - name: Commit updated baselines | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.update-baselines }} | |
| if: ${{ always() && steps.record.outcome != 'skipped' && github.event_name == 'workflow_dispatch' && inputs.update-baselines }} | |
| # always(): freshly recorded baselines must be committed even when a | |
| # test failed mid-run (a red test does not invalidate the OTHER pages' | |
| # recordings). Gated on the record step actually having run, and the | |
| # commit is a no-op when nothing changed. | |
| - name: Commit updated baselines | |
| if: ${{ always() && !cancelled() && steps.record.outcome != 'skipped' && github.event_name == 'workflow_dispatch' && inputs.update-baselines }} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/test.yml around lines 119 - 124, Update the “Commit
updated baselines” step condition to allow completed successful or failed
recording steps while explicitly excluding a cancelled steps.record outcome.
Preserve the existing always(), workflow_dispatch, and inputs.update-baselines
guards.
What
Round-3 DevX, part 2 (follows #422): fixes the CI pipeline's correctness hazards and its biggest cost leaks. All findings verified against the live workflows; two were observed failing in real runs this session.
Correctness
hugo_stats.jsoncache is now exact-key-only (own cache entry insetup-hugo, norestore-keys). The PurgeCSS warm-up skip trusts any non-empty stats file, but arestore-keyspartial hit could restore a stale stats file from an older tree — re-opening the purge-live-classes incident class (2026-07-19.sr-only) through the cache. An exact hit means the source tree is identical, so the stats are valid; any change misses and pays the ~52s warm-up instead of risking a broken deploy.test.ymlPR runs check out the merge commit (default checkout) instead ofhead_ref— the head-only checkout tested the tip without the base merged in and breaks on fork PRs. Record dispatches still check out the branch ref, which the baseline push needs.rake test:systemonly (the fullrake testmeant a unit failure aborted the run after 15+ minutes of recording, discarding every PNG), and the commit step isalways()-gated + porcelain-guarded, so recorded baselines land even when one page's test is red.layouts/**andassets/**(visual gate) and rootlayouts/**(link check) — root-level template/asset overrides previously skipped both gates.Cost
publish.ymlandlink-check.ymlworkflow_runjobs now skip when "Sync articles" pushed nothing, viagithub.event.workflow_run.head_sha != github.sha(forworkflow_runeventsgithub.shais the current default-branch head — a sync push is exactly what moves them apart). The sync fires every 10 minutes 8-21 UTC and usually commits nothing; that was ~84 no-op deploy + test + link-crawl cascades per day._destdropped from the setup-hugo cache — it uploaded ~1-2 GB per job and evicted every other cache from the 10 GB repo quota; the build step regenerates it anyway.build: 'false'andrake test:linksdoes its own production build. The double build blew the job's 10-minute timeout on cold caches — observed live on fix(devx): R3-1 - one truthful build path for every test runner #422 (job 91200611137, killed mid-build at 10m; the warm re-run passed in 3m19s).Deliberately not done
test.ymlpermissions per-step: GitHub has no step-level permissions and the single job serves both PR (needspull-requests: writefor the report comment) and record (needscontents: write) events; splitting the job would duplicate the whole step list. Fork PRs already receive a read-only token automatically.Verification
workflow_runevents (schedule/push/PR/dispatch behavior unchanged).bin/hugo-build's own cold-start guard covers the miss path (verified in R3-1).rake test:linksalready builds viabin/hugo-buildinto its ownOUTPUT_DIR(Rakefilebuild_for_linkcheck); the composite build it replaces produced a tree nothing in this job read.Generated by Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation