Skip to content

ci: add daily scheduled Test run that posts results to Slack#440

Merged
MegaRedHand merged 4 commits into
mainfrom
ci/daily-test-slack
Jun 16, 2026
Merged

ci: add daily scheduled Test run that posts results to Slack#440
MegaRedHand merged 4 commits into
mainfrom
ci/daily-test-slack

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

What

Adds a Daily CI Test workflow that runs the workspace test suite on a 0 12 * * * (12:00 UTC) cron and posts the result (pass/fail) to Slack.

To avoid duplicating the ~70 lines of fixture-download + Rust setup logic, the CI Test job body is extracted into a reusable composite action (.github/actions/run-fixture-tests) that both ci.yml and the new daily workflow consume.

Changes

File Change
.github/actions/run-fixture-tests/action.yml New composite action: fetch+cache leanSpec fixtures, setup Rust, make test. Extracted verbatim from CI's Test job.
.github/workflows/ci.yml Test job now uses: ./.github/actions/run-fixture-tests. Behavior unchanged.
.github/workflows/daily_ci.yml New scheduled workflow + notify job (if: always()) posting to Slack with a link to the run.

Slack wiring

Mirrors daily_loc_report.yml:

  • Scheduled runs → ETHLAMBDA_GENERAL_SLACK_WEBHOOK (prod)
  • Manual workflow_dispatch → defaults to ETHLAMBDA_TEST_SLACK_WEBHOOK (test channel), selectable via target input

No new secrets required.

Testing

  • YAML validated locally.
  • Use workflow_dispatch (target: test) to dry-run against the test channel before the first scheduled run.

Adds a `Daily CI Test` workflow on a 12:00 UTC cron that runs the
workspace test suite and reports pass/fail to Slack, so a green main
is verified daily independent of PR activity.

To avoid duplicating the ~70 lines of fixture-download + Rust setup
logic, the CI Test job body is extracted into a reusable composite
action (`.github/actions/run-fixture-tests`) that both `ci.yml` and the
new daily workflow consume. The CI Test job behavior is unchanged.

Slack wiring mirrors daily_loc_report.yml: scheduled runs post to the
prod webhook, manual workflow_dispatch defaults to the test channel.
The notify job runs with `if: always()` so failures are reported too.
@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

Overall Assessment: This PR cleanly extracts fixture test logic into a reusable composite action and adds daily CI with Slack notifications. The changes improve maintainability but introduce minor security and reliability considerations typical of CI/CD workflows.

Security & Reliability

1. Supply Chain Risk — Unpinned Third-Party Actions

File: .github/actions/run-fixture-tests/action.yml
Lines: 64, 67

The composite action uses mutable references for third-party actions:

  • dtolnay/rust-toolchain@masterCritical: This follows the default branch; a compromised or buggy push immediately affects your CI.
  • Swatinem/rust-cache@v2 — Less critical but still mutable.

Recommendation: Pin to specific commit SHAs:

uses: dtolnay/rust-toolchain@7f2b3f8a6e1f4a5c2d8e9f0a1b2c3d4e5f6a7b8c  # v1.92.0

2. Fragile JSON Parsing Without Validation

File: .github/actions/run-fixture-tests/action.yml
Lines: 14-16

The Python one-liners use next() without a default value. If the release assets change names or the API structure shifts, StopIteration will propagate as a Python traceback rather than a clear CI error.

Recommendation: Add error handling:

fixtures_url=$(echo "$json" | python3 -c "
import sys, json
j = json.load(sys.stdin)
assets = j.get('assets', [])
url = next((a.get('browser_download_url') for a in assets if a.get('name') == 'fixtures-prod-scheme.tar.gz'), None)
if not url:
    print('Error: fixtures-prod-scheme.tar.gz not found in release assets', file=sys.stderr)
    sys.exit(1)
print(url)
")

3. Missing Referenced Script

File: .github/workflows/daily_ci.yml
Line: 80

The workflow executes bash .github/scripts/publish_slack.sh, but this file is not present in the diff. Ensure this script:

  • Validates the JSON payload before sending
  • Does not leak the webhook URL to logs
  • Handles curl failures gracefully

4. Workspace Path Assumptions

File: .github/actions/run-fixture-tests/action.yml
Line: 52-53

rm -rf leanSpec/fixtures assumes the working directory is the repository root. While GitHub Actions defaults to this, explicit context is safer:

rm -rf "${{ github.workspace }}/leanSpec/fixtures"

Correctness & Maintainability

5. Cache Poisoning Protection

File: .github/actions/run-fixture-tests/action.yml
Lines: 28-36, 58-65

The logic to save cache only on successful download (steps.download-fixtures.outcome == 'success') correctly prevents poisoning from partial downloads. The always() condition ensures caching happens even if tests fail, which is appropriate for expensive fixture downloads.

6. Daily CI Concurrency

File: .github/workflows/daily_ci.yml
Lines: 22-24

The concurrency group prevents overlapping daily runs, which is correct for resource-intensive consensus tests. However, consider adding cancel-in-progress: true for the test job specifically if you want faster feedback on manual triggers, while keeping the daily schedule protected.

7. Slack Webhook Selection Logic

File: .github/workflows/daily_ci.yml
Lines: 78-80

The ternary expression for webhook selection is correct:

${{ (github.event_name == 'schedule' || inputs.target == 'prod') && secrets.ETHLAMBDA_GENERAL_SLACK_WEBHOOK || secrets.ETHLAMBDA_TEST_SLACK_WEBHOOK }}

The choice input type restricts inputs.target to test or prod, preventing injection. Good.

Minor Improvements

8. Shell Quoting Consistency

While most variables are properly quoted, line 52-53 in the action use unquoted static paths. Though low risk, consistent quoting is better practice.

9. Python vs jq

Using Python for JSON parsing is portable but verbose. If jq is acceptable (available on ubuntu-latest), it reduces attack surface and complexity:

fixtures_url=$(echo "$json" | jq -r '.assets[] | select(.name=="fixtures-prod-scheme.tar.gz") | .browser_download_url')

Consensus-Specific Considerations

Since this PR only touches CI infrastructure, there are no direct changes to attestation processing, XMSS signatures, or fork choice. However, ensure that the make test command invoked in the composite action runs all consensus-critical tests (justification/finalization, state transitions, SSZ round-trips), as skipping these in the daily CI would create a security blind spot.

Status: Approve with minor comments. The SHA256 verification of fixtures and proper cache invalidation logic are well-implemented.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

No consensus-path Rust code is touched here; this PR is CI/workflow-only. I found a few infrastructure issues worth fixing before merge.

  1. High: .github/workflows/daily_ci.yml checks out the selected ref and then runs .github/scripts/publish_slack.sh with SLACK_WEBHOOK in the environment. On workflow_dispatch, that means repo-controlled shell from whatever ref the operator selected executes with a secret available. A malicious branch can trivially exfiltrate the webhook. The notify job does not need checkout at all; inline the curl or otherwise ensure the secret-consuming code comes from the trusted default branch only.

  2. Medium: .github/workflows/daily_ci.yml uses cond && prod_secret || test_secret to choose the webhook. If the prod secret is unset/empty on a scheduled run or target=prod, the expression silently falls back to the test webhook instead of failing. That misroutes production notifications and hides the misconfiguration. Split this into an explicit conditional branch and validate the chosen secret directly.

  3. Low: .github/workflows/daily_ci.yml builds JSON via heredoc with raw interpolation of ${{ github.ref_name }}. Ref names can contain characters that break JSON quoting, which makes the Slack post fail or produce malformed content. Generate the payload with jq -n or Python JSON encoding instead of string templating.

Aside from those points, the extracted composite action in .github/actions/run-fixture-tests/action.yml looks like a reasonable cleanup, and the cache-save ordering is sensible.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Here is my analysis of PR #440:


PR #440 Review: Daily Scheduled CI Test with Slack Notification

Overview

This PR refactors the existing CI test job into a reusable composite action (.github/actions/run-fixture-tests) and adds a new daily_ci.yml workflow that runs the full test suite on a daily cron (0 12 * * *) and posts pass/fail results to Slack. The approach is clean and the behavior of the existing ci.yml Test job is unchanged.


Correctness

Composite action extraction (action.yml)

  • The step ordering (fetch release info → restore cache → download if miss → save cache → touch → setup Rust → run tests) is correct and matches the original.
  • shell: bash is required on all run: steps inside a composite action and is properly present on every step. ✅
  • steps.cache-fixtures.outputs.cache-primary-key is the correct output name from actions/cache/restore@v5 to pass back to actions/cache/save@v5. ✅
  • The "save only on download success" guard (steps.download-fixtures.outcome == 'success') correctly prevents caching a partial or failed download. ✅
  • The touch leanSpec/fixtures step is well-explained and correctly placed after the download step. ✅
  • The quoted >> "$GITHUB_OUTPUT" in the composite action is a minor correctness improvement over the original's unquoted >> $GITHUB_OUTPUT. ✅

Daily workflow (daily_ci.yml)

  • needs: test + if: always() on the notify job is the correct pattern to ensure the Slack notification fires even when test fails or is cancelled. ✅
  • concurrency.cancel-in-progress: false prevents a late-starting daily run from being silently dropped mid-flight. ✅
  • The Slack webhook conditional:
    (github.event_name == 'schedule' || inputs.target == 'prod')
    && secrets.ETHLAMBDA_GENERAL_SLACK_WEBHOOK
    || secrets.ETHLAMBDA_TEST_SLACK_WEBHOOK
    
    Logic is correct for the intended routing. One edge case: if ETHLAMBDA_GENERAL_SLACK_WEBHOOK is unset (empty string), the expression silently falls through to the test webhook even on a scheduled prod run, because true && '' || 'test-url' evaluates to 'test-url'. Consider adding a dedicated step that validates the selected webhook is non-empty before posting.

Security

Supply-chain risks (both workflows)

  • dtolnay/rust-toolchain@master uses a floating branch ref, not a pinned SHA. This is a supply-chain attack vector — a compromised push to master would affect all runs immediately. Pinning to a version tag (e.g. @stable) or a full commit SHA is the safer pattern.

  • Swatinem/rust-cache@v2 is similarly unpinned beyond the major version. Less critical than @master, but worth noting for consistency.

Unauthenticated GitHub API call

The fixture release info fetch:

curl -sL "https://api.github.com/repos/leanEthereum/leanSpec/releases/latest"

runs unauthenticated, which is subject to GitHub's 60 requests/hour rate limit for the runner's IP. When multiple workflow runs share a runner pool this can hit the limit. Passing Authorization: Bearer ${{ github.token }} (or -H "Authorization: token ${{ github.token }}") raises the limit to 5,000/hour and is a trivial change.

Heredoc JSON injection

In daily_ci.yml, the Slack payload is built via a heredoc with unescaped context interpolation:

"text": "$emoji *Daily CI Test* $status on \`${{ github.ref_name }}\`\n<$run_url|View run #${{ github.run_number }}>"

github.run_number is always an integer — safe. github.ref_name is user-controlled for workflow_dispatch runs (any branch can trigger it). A branch name containing ", \, or a newline would break the JSON silently or cause curl to send a malformed payload. For scheduled runs on main this is a non-issue, but for manual dispatches from a feature branch it could fail. Consider using a JSON-safe method (e.g. jq -n --arg text "..." '$text') or at minimum adding a | jq . validation step before posting.


Minor / Style

  • The status shell variable is set in the case block, then interpolated into the heredoc alongside $emoji. Both are controlled by the TEST_RESULT value from GitHub Actions context (only ever success | failure | cancelled | *), so there is no injection risk here. ✅

  • The notify job checks out the repo solely to get .github/scripts/publish_slack.sh. This is correct and expected given the script is versioned alongside the workflow. ✅

  • The permissions: contents: read at the workflow level is appropriately minimal. ✅


Summary

The core design is sound: the composite action extraction is clean, the cache logic is correct, and the daily workflow correctly handles failure notification. The main actionable items are:

  1. Pin dtolnay/rust-toolchain@master to a tag or commit SHA to reduce supply-chain risk.
  2. Add a Authorization: Bearer ${{ github.token }} header to the GitHub API curl call to avoid rate-limiting.
  3. Consider using jq for JSON construction in the Slack payload to guard against special characters in github.ref_name on manual dispatch runs.
  4. (Optional) Guard against a silently unconfigured ETHLAMBDA_GENERAL_SLACK_WEBHOOK on scheduled runs.

Automated review by Claude (Anthropic) · sonnet · custom prompt

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a daily scheduled CI test workflow that runs the full workspace test suite at 12:00 UTC and posts pass/fail results to Slack. To avoid duplicating ~70 lines of fixture-download and Rust setup logic, that logic is extracted into a new reusable composite action (.github/actions/run-fixture-tests) consumed by both ci.yml and the new daily_ci.yml.

  • Composite action (.github/actions/run-fixture-tests/action.yml): verbatim extraction of the existing Test job steps; correctly adds shell: bash to all run steps as required by composite actions.
  • ci.yml: Test job body replaced with a single uses: call to the composite action — behavior is unchanged.
  • daily_ci.yml: Scheduled workflow with a notify job (if: always()) that posts to Slack; webhook routing and concurrency config mirror the existing daily_loc_report.yml.

Confidence Score: 4/5

Safe to merge — the composite action is a clean extraction with no behavioral changes to the existing CI, and the daily workflow wires Slack notifications correctly following the established pattern.

The refactor of the Test job into a composite action is mechanical and well-executed. The daily workflow mirrors daily_loc_report.yml faithfully. The one issue worth noting is that the Slack JSON payload is assembled via heredoc with ${{ github.ref_name }} embedded directly, which could produce malformed JSON if the ref name ever contains characters that break a JSON string — a low-probability scenario on GitHub but worth hardening with jq.

.github/workflows/daily_ci.yml — specifically the Build Slack payload step where the JSON is constructed via heredoc with unescaped context values.

Important Files Changed

Filename Overview
.github/actions/run-fixture-tests/action.yml New composite action extracting fixture-download + Rust-setup + test-run logic from ci.yml. Logic is verbatim from the original job; adds shell: bash to all run steps (a correct requirement for composite actions), and the cache-save guard is preserved correctly.
.github/workflows/ci.yml Test job body replaced with a single uses: ./.github/actions/run-fixture-tests call. Behavior is unchanged; the composite action step does not need extra inputs or permissions beyond what the job already has.
.github/workflows/daily_ci.yml New daily-scheduled workflow. Slack-webhook routing and concurrency config mirror daily_loc_report.yml correctly. One P2: the JSON payload is built via heredoc with unescaped ${{ github.ref_name }} — consider using jq for safe construction.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Cron as Cron / workflow_dispatch
    participant Test as Job: test
    participant Action as .github/actions/run-fixture-tests
    participant GH as GitHub API (leanSpec)
    participant Cache as Actions Cache
    participant Notify as Job: notify
    participant Slack as Slack Webhook

    Cron->>Test: trigger (12:00 UTC or manual)
    Test->>Action: uses: run-fixture-tests
    Action->>GH: GET /releases/latest (fixtures URL + SHA)
    Action->>Cache: "restore leanspec-fixtures-{sha}"
    alt cache miss
        Action->>GH: curl fixtures tarball + .sha256
        Action->>Action: verify SHA256
        Action->>Cache: "save leanspec-fixtures-{sha}"
    end
    Action->>Action: make test (Rust workspace)
    Action-->>Test: outcome (success / failure)
    Test-->>Notify: needs.test.result
    Notify->>Notify: build Slack JSON payload
    Notify->>Slack: POST ci_result_slack.json
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Cron as Cron / workflow_dispatch
    participant Test as Job: test
    participant Action as .github/actions/run-fixture-tests
    participant GH as GitHub API (leanSpec)
    participant Cache as Actions Cache
    participant Notify as Job: notify
    participant Slack as Slack Webhook

    Cron->>Test: trigger (12:00 UTC or manual)
    Test->>Action: uses: run-fixture-tests
    Action->>GH: GET /releases/latest (fixtures URL + SHA)
    Action->>Cache: "restore leanspec-fixtures-{sha}"
    alt cache miss
        Action->>GH: curl fixtures tarball + .sha256
        Action->>Action: verify SHA256
        Action->>Cache: "save leanspec-fixtures-{sha}"
    end
    Action->>Action: make test (Rust workspace)
    Action-->>Test: outcome (success / failure)
    Test-->>Notify: needs.test.result
    Notify->>Notify: build Slack JSON payload
    Notify->>Slack: POST ci_result_slack.json
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
.github/workflows/daily_ci.yml:56-72
**Heredoc JSON construction embeds expressions without sanitization**

`${{ github.ref_name }}` is expanded by the Actions expression engine before the shell runs, and the resulting string is written verbatim into a JSON value without escaping. If the ref name ever contains a `"` or `\` character (unlikely on GitHub but possible with other git hosting), the output file will be malformed JSON and the subsequent `curl` to Slack will fail silently (the `publish_slack.sh` script doesn't validate JSON before posting). Using `jq` to assemble the payload is the standard mitigation — it handles all escaping automatically. The `$run_url` and `${{ github.run_number }}` expansions share the same pattern but are lower risk since those values are always safe strings.

Reviews (1): Last reviewed commit: "ci: add daily scheduled Test run that po..." | Re-trigger Greptile

Comment thread .github/workflows/daily_ci.yml Outdated
Comment on lines +56 to +72
cancelled) emoji="⚠️"; status="was cancelled";;
*) emoji="❔"; status="finished with status \`$TEST_RESULT\`";;
esac
run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
cat > ci_result_slack.json <<EOF
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "$emoji *Daily CI Test* $status on \`${{ github.ref_name }}\`\n<$run_url|View run #${{ github.run_number }}>"
}
}
]
}
EOF

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Heredoc JSON construction embeds expressions without sanitization

${{ github.ref_name }} is expanded by the Actions expression engine before the shell runs, and the resulting string is written verbatim into a JSON value without escaping. If the ref name ever contains a " or \ character (unlikely on GitHub but possible with other git hosting), the output file will be malformed JSON and the subsequent curl to Slack will fail silently (the publish_slack.sh script doesn't validate JSON before posting). Using jq to assemble the payload is the standard mitigation — it handles all escaping automatically. The $run_url and ${{ github.run_number }} expansions share the same pattern but are lower risk since those values are always safe strings.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/daily_ci.yml
Line: 56-72

Comment:
**Heredoc JSON construction embeds expressions without sanitization**

`${{ github.ref_name }}` is expanded by the Actions expression engine before the shell runs, and the resulting string is written verbatim into a JSON value without escaping. If the ref name ever contains a `"` or `\` character (unlikely on GitHub but possible with other git hosting), the output file will be malformed JSON and the subsequent `curl` to Slack will fail silently (the `publish_slack.sh` script doesn't validate JSON before posting). Using `jq` to assemble the payload is the standard mitigation — it handles all escaping automatically. The `$run_url` and `${{ github.run_number }}` expansions share the same pattern but are lower risk since those values are always safe strings.

How can I resolve this? If you propose a fix, please make it concise.

The heredoc embedded ${{ github.ref_name }} (user-controlled on
workflow_dispatch) directly into a JSON value. A ref name containing a
quote, backslash, or newline would produce malformed JSON and a silent
Slack failure. Pass context values through the environment and assemble
the payload with jq, which escapes them correctly.

Addresses PR #440 review comment r3423800540.
@MegaRedHand MegaRedHand merged commit 60a2567 into main Jun 16, 2026
3 checks passed
@MegaRedHand MegaRedHand deleted the ci/daily-test-slack branch June 16, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants