Skip to content

feat: ci send nightly-e2e scorecard to slack#4308

Merged
cv merged 17 commits into
mainfrom
feat/ci-send-nightly-scorecard-to-slack
Jun 2, 2026
Merged

feat: ci send nightly-e2e scorecard to slack#4308
cv merged 17 commits into
mainfrom
feat/ci-send-nightly-scorecard-to-slack

Conversation

@hunglp6d

@hunglp6d hunglp6d commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the Slack notification piece of the nightly E2E scorecard
(issue #2614). Run-mode-aware routing posts the Block Kit scorecard to
different Slack channels:

  • Schedule runs (cron) → #nemoclaw-situation-room (operational alerts)
  • Manual full runs (workflow_dispatch with empty jobs) → #nemoclaw-ci (team-wide visibility)

Related Issue

Refs #2612.
Closes #2614.

Changes

  • New scripts/scorecard/build-slack-blocks.js — pure CommonJS builder for the Slack payload (buildBlocks, buildFallbackText, getStatusColor, getSlackChannel); no actions/github-script dependencies so the module stays unit-testable.
  • New test/scorecard-blocks.test.ts — 22 unit tests across perfect / failure / selective-dispatch scenarios, fallback text, color mapping, channel routing, and total-ran prefix.
  • .github/workflows/nightly-e2e.yaml:
    • scorecard job sparse-checks out scripts/scorecard so the Slack step can require() the builder.
    • Generate nightly scorecard step emits a structured scorecardData JSON output alongside the existing Markdown summary, including per-job html_url for failed-jobs hyperlinks (best-effort via listJobsForWorkflowRun).
    • Post scorecard to Slack step wraps blocks in a legacy attachment for a coloured left-edge bar (green / red / yellow) signalling run health at a glance.
    • Routing happens inside the script using the canonical runMode computed by the generate step — single source of truth, aligns with the trim+filter classification Yaunches already established.
    • Failed jobs are rendered as Slack mrkdwn hyperlinks (<url|name>) pointing directly to each failed job's logs.
    • Stats line begins with Total ran: <ran>/<total> for quick eyeball of skip-vs-run ratio.
    • New post_to_slack workflow_dispatch input — only relevant for selective dispatches (production runs ignore it).

An instance

CleanShot 2026-05-27 at 18 25 44@2x

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Hung Le hple@nvidia.com

Summary by CodeRabbit

  • New Features

    • Opt-in control to post nightly scorecards to Slack for manual/preview runs (default: off).
    • Scorecards now emit structured JSON enabling richer Slack notifications: colored status, stats, trend, requested-job context for selective runs, failed-job links when available, and action buttons to view runs.
    • Slack routing selects channel based on run type and skips preview posts unless enabled.
  • Tests

    • Added tests for Slack payload generation, fallback titles, color mapping, channel routing, and multiple run scenarios (perfect, failed, selective).

Review Change Stack

@copy-pr-bot

copy-pr-bot Bot commented May 27, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔄 Running review...
📝 Walkthrough

Walkthrough

Updates the nightly-e2e workflow to emit structured scorecard JSON, adds a Slack Block Kit builder module and tests, and posts rich Slack messages on schedule or for manual dispatch when post_to_slack is enabled.

Changes

Nightly scorecard Slack Block Kit integration

Layer / File(s) Summary
Slack block builder module
scripts/scorecard/build-slack-blocks.js
Exports buildBlocks(data) to construct ordered Slack Block Kit blocks (context, stats, perfect-run or failed-jobs section, trend, actions); buildFallbackText(data) for the notification title; getStatusColor(data) to map outcomes to Slack colors; and getSlackChannel(data) to select channel routing.
Slack block builder test suite
test/scorecard-blocks.test.ts
Vitest tests with makeData() fixture covering perfect runs, failed runs, and selective dispatch. Validates block structure, failed-job listings (with/without URLs), button styling/URLs, trend text handling, fallback title, and color/channel mapping.
Nightly workflow Slack posting integration
.github/workflows/nightly-e2e.yaml
Adds workflow_dispatch.inputs.post_to_slack (boolean, default false); sparse-checkout of scripts/scorecard; builds per-job failedJobs with optional html_url via the Actions jobs API; updates markdown formatting for cancelled/run breakdown; sets scorecardData output (JSON) instead of the previous string; and replaces Slack step to parse SCORECARD_DATA, use the builder module to produce blocks/attachments and channel selection, gate preview posts on post_to_slack, and POST JSON to the selected webhook.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as nightly-e2e
  participant ScoreJob as scorecard job
  participant Builder as build-slack-blocks.js
  participant SlackAPI as Slack Webhook
  Workflow->>ScoreJob: trigger (schedule or dispatch)
  ScoreJob->>ScoreJob: generate scorecard JSON (scorecardData)
  ScoreJob->>Builder: require() and pass SCORECARD_DATA
  Builder->>ScoreJob: return blocks + fallback + color + channel
  ScoreJob->>SlackAPI: POST block kit JSON payload
  SlackAPI-->>ScoreJob: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

CI/CD, Integration: Slack

Suggested reviewers

  • cv
  • ericksoa
  • jyaunches

Poem

🐰 I hopped through code at break of day,
Blocks for Slack to show the play,
Links and colors, neatly spun,
Morning scorecard—job well done!
A tiny rabbit's build—now run.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding Slack notification support to the nightly E2E scorecard workflow.
Linked Issues check ✅ Passed The PR implements all coding requirements from #2614: Slack Block Kit message formatting with headers, stats, job failures, trends, and actions; conditional posting when SLACK_WEBHOOK_URL exists; support for scheduled and manual (workflow_dispatch) triggers with opt-in control.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Slack integration feature. The sparse checkout, JSON output restructuring, and block builder are necessary to support the Slack posting functionality described in #2614.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ci-send-nightly-scorecard-to-slack

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: None
Optional E2E: None

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • None. No existing E2E jobs are required because this PR only changes CI scorecard/Slack reporting and its unit tests. It cannot affect runtime NemoClaw user flows such as install/onboarding, sandbox lifecycle, credentials, policy enforcement, inference routing, deployment, or assistant interactions.

Optional E2E

  • None.

New E2E recommendations

  • ci-reporting-scorecard (low): There is no existing E2E job that specifically validates the nightly scorecard Slack-posting path, webhook channel selection, or workflow_dispatch post_to_slack behavior. Current coverage is unit-level for the block builder plus workflow execution only when other E2E jobs are dispatched.
    • Suggested test: Add a lightweight workflow-level smoke test or mocked dispatch test for the nightly scorecard Slack notification path, including selective dispatch with post_to_slack=false/true and scheduled/manual channel routing.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

E2E Scenario Advisor Recommendation

Required scenario E2E: None
Optional scenario E2E: None

Workflow run

Full scenario advisor summary

E2E Scenario Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required scenario E2E

  • None. No scenario E2E is recommended because the changes are limited to the legacy nightly E2E workflow scorecard/Slack reporting helper and its unit tests, with no changes under test/e2e-scenario/ or .github/workflows/e2e-scenarios*.yaml and no directly scenario-relevant paths.

Optional scenario E2E

  • None.

Relevant changed files

  • None.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor

Findings: 3 needs attention, 11 worth checking, 1 nice ideas
Since last review: 0 prior items resolved, 14 still apply, 1 new item found

Review findings

🛠️ Needs attention

  • Untrusted workflow_dispatch input can reach the Slack webhook secret boundary (.github/workflows/nightly-e2e.yaml:2281): The generate step still interpolates the untrusted workflow_dispatch inputs.jobs value directly into JavaScript source. A crafted value containing quotes or newlines can break out of the generated string. The same job checks out workspace code, and the later Slack step maps three Slack webhook secrets into the environment before requiring the checked-out builder, so an injection can tamper with code that later executes in the secret-bearing step.
    • Recommendation: Pass inputs.jobs through an environment variable or JSON-safe serialization, validate it against the dispatchable-job allowlist with length and character limits, and keep the Slack secret-bearing step minimal and trusted. Avoid importing mutable workspace code after webhook secrets are present, or load only trusted inline/pinned code.
    • Evidence: const requestedJobsRaw = isDispatch ? '${{ inputs.jobs }}'.trim() : ''; later the Slack step maps SLACK_WEBHOOK_URL_SITUATION_ROOM, SLACK_WEBHOOK_URL_CI, and SLACK_WEBHOOK_URL_PREVIEW from secrets and require()s scripts/scorecard/build-slack-blocks.ts from GITHUB_WORKSPACE.
  • Slack scorecard does not satisfy issue feat(ci): connect nightly scorecard to Slack channel #2614 literally (.github/workflows/nightly-e2e.yaml:2453): Issue feat(ci): connect nightly scorecard to Slack channel #2614 asks for SLACK_WEBHOOK_URL, daily posting at about 8am ET, a header block with date, top flaky jobs, run counts, perfect count, flaky jobs, trend, a link to Actions, and manual workflow_dispatch Slack posting for testing. The current diff uses three channel-specific webhook secrets, keeps the midnight UTC cron, intentionally omits a header block, renders current failed jobs instead of flaky jobs, has no explicit perfect count, and makes selective-dispatch Slack posting opt-in.
    • Recommendation: Either implement the feat(ci): connect nightly scorecard to Slack channel #2614 clauses literally or update/split the tracked acceptance source with maintainer-confirmed behavior for channel-specific secrets, the situation-room/CI/preview routing model, fallback-title/no-header payload, failed-job links instead of flaky jobs, the desired schedule, and selective-dispatch opt-in posting.
    • Evidence: Workflow cron remains 0 0 * * *; Slack env uses SLACK_WEBHOOK_URL_SITUATION_ROOM, SLACK_WEBHOOK_URL_CI, and SLACK_WEBHOOK_URL_PREVIEW; test/scorecard-blocks.test.ts asserts no header block; buildBlocks() renders failedJobs rather than top flaky jobs.
  • Scorecard checkout lacks job-level contents permission (.github/workflows/nightly-e2e.yaml:2253): The scorecard job declares job-level permissions with only actions: read and then runs actions/checkout to load the scorecard builder. Job-level permissions override the workflow-level contents: read permission, so the new checkout can fail before scorecard generation or Slack posting.
    • Recommendation: Add contents: read to the scorecard job permissions, or avoid checkout in this path by embedding the trusted Slack posting code in the workflow step.
    • Evidence: scorecard.permissions contains actions: read only; the next step uses actions/checkout with sparse-checkout: scripts/scorecard.

🔎 Worth checking

  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml failed-job URL API catch and null fallback: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: listJobsForWorkflowRun is caught with a warning, urlByName maps j.name to j.html_url, and missing URLs become null.
  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml prior-day trend API fallback: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: The workflow catches listWorkflowRuns errors and sets trendLine = `Trend: ⊘ Could not fetch prior-day data (${e.message})`.
  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml legacy Slack attachment wrapper: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: The Slack post step comments Legacy attachment wrapper enables the coloured left-edge bar and constructs payload.attachments[0].
  • Source-of-truth review needed: scripts/scorecard/build-slack-blocks.ts fallback title and no-header behavior: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: buildFallbackText() returns the title and tests assert blocks.some((block) => block.type === 'header') is false.
  • Source-of-truth review needed: scripts/scorecard/build-slack-blocks.ts failed-job URL fallback rendering: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: buildBlocks() renders a code-formatted job name when job.url is null; the test fixture comments url=null exercises fallback rendering.
  • Slack payload fields are rendered without schema validation, escaping, or URL validation (scripts/scorecard/build-slack-blocks.ts:114): The Slack builder interpolates requested job names, failed job names, failed job URLs, trend text, and run URLs directly into Slack mrkdwn/buttons. SCORECARD_DATA is parsed without runtime schema validation before these fields are rendered. Some values are generated locally, but requested jobs originate from workflow_dispatch input and API error text can enter trendLine.
    • Recommendation: Validate SCORECARD_DATA at runtime, allowlist job names, bound array and string lengths, validate URLs as expected GitHub Actions URLs, reject unknown runMode values, and escape Slack mrkdwn text before rendering.
    • Evidence: buildBlocks() renders job.url, job.name, data.trendLine, and data.runUrl directly; .github/workflows/nightly-e2e.yaml calls JSON.parse(process.env.SCORECARD_DATA) without schema validation; getSlackChannel() routes unknown run modes to preview.
  • Slack webhook failure logging includes the remote response body (.github/workflows/nightly-e2e.yaml:2519): The Slack post step logs the full response body from a secret-authenticated webhook request when Slack returns a non-OK status. Even if Slack normally returns benign text, logging remote response bodies broadens leakage and log-injection exposure.
    • Recommendation: Log bounded, sanitized details such as status and statusText. Avoid printing response bodies from secret-authenticated webhook calls unless they are explicitly known safe and size-limited.
    • Evidence: core.warning(`Slack webhook (${channel}) returned ${resp.status}: ${await resp.text()}`);
  • Slack posting lacks workflow-contract and negative-path coverage (test/scorecard-blocks.test.ts:29): The added tests exercise the pure builder's happy paths and one null-URL fallback, but they do not cover the workflow contract or security-sensitive negative paths. Missing coverage includes safe dispatch-input serialization, requested-job allowlisting, checkout permissions, SCORECARD_DATA validation, Slack routing/gating, attachment wrapper shape, malformed URLs, oversized lists, API failure fallbacks, and non-OK webhook handling.
    • Recommendation: Add YAML/source-shape tests for the workflow and negative builder tests for malicious requested job names, invalid structured data, malformed URLs, overlong lists, invalid run modes, Slack mrkdwn/link injection, and response logging. If posting remains inline in workflow code, assert the secret-boundary contract.
    • Evidence: The test imports only scripts/scorecard/build-slack-blocks.ts; workflow behavior such as post_to_slack, webhook secrets, direct expression interpolation, checkout permissions, payload construction, and response logging is not asserted.
  • Failed-job hyperlinks may silently miss reusable workflow jobs (.github/workflows/nightly-e2e.yaml:2318): The hyperlink lookup maps GitHub Actions job display names to html_url, then looks up entries by workflow needs keys. Reusable workflow job display names can differ from caller job IDs, and the lookup fetches only the first 100 jobs without pagination, so failed jobs may often render unlinked.
    • Recommendation: Verify actual job names returned by listJobsForWorkflowRun for this workflow and map them robustly to needs keys, or preserve job URLs from a workflow-supported source. Add a fixture or workflow-contract test covering reusable workflow names and pagination.
    • Evidence: failedNames are derived from Object.entries(needs), urlByName is built from jobsData.jobs.map((j) => [j.name, j.html_url]), and the API call uses per_page: 100 without pagination.
  • Localized fallback and compatibility paths need source-of-truth documentation and tests (.github/workflows/nightly-e2e.yaml:2501): The PR adds several tolerant or compatibility paths without fully documenting the invalid state handled, source boundary, why the source cannot be fixed here, regression test, or removal condition. This includes API failures becoming warnings/fallback text, missing failed-job URLs becoming null, Slack blocks being wrapped in a legacy attachment for color, and moving the issue-requested header into fallback text while tests assert no header block.
    • Recommendation: Add source-of-truth comments and workflow/source-shape tests that preserve the intended payload contract: API failure behavior, URL-null rendering, payload text, attachments[0].color, attachments[0].blocks, and no-header fallback title. If these choices supersede feat(ci): connect nightly scorecard to Slack channel #2614, update the tracked acceptance source.
    • Evidence: The workflow catches listJobsForWorkflowRun errors and maps missing URLs to null, catches listWorkflowRuns errors into Trend: ⊘ Could not fetch prior-day data (...), comments Legacy attachment wrapper enables the coloured left-edge bar, and constructs payload.attachments[0]; tests assert no header and null-URL fallback rendering.
  • Workflow runtime is not proven to load the TypeScript builder (.github/workflows/nightly-e2e.yaml:2475): The Slack step requires scripts/scorecard/build-slack-blocks.ts directly from actions/github-script. The unit tests run through Vitest, which can transform TypeScript, but they do not prove the GitHub Actions runtime will load this .ts file the same way. The PR body also describes a build-slack-blocks.js file, while the diff adds .ts.
    • Recommendation: Either compile/check in a JavaScript module for the workflow to require, use a runtime explicitly known to support this TypeScript loading mode, or add a workflow/source-shape test documenting the intended loader contract.
    • Evidence: require(path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/build-slack-blocks.ts')); tests use createRequire inside a Vitest test file rather than the actions/github-script runtime.

🌱 Nice ideas

  • Refresh stale scorecard workflow comments (.github/workflows/nightly-e2e.yaml:2189): The scorecard comments still say the job only runs on schedule and not workflow_dispatch, but the job condition now allows both schedule and workflow_dispatch.
    • Recommendation: Update the nearby workflow comments to match the scheduled, manual full, and selective-dispatch Slack behavior.
    • Evidence: Comment says Only runs on schedule (not workflow_dispatch — that uses report-to-pr), while the job condition is github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'.
Since last review details

Current findings:

  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml failed-job URL API catch and null fallback: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: listJobsForWorkflowRun is caught with a warning, urlByName maps j.name to j.html_url, and missing URLs become null.
  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml prior-day trend API fallback: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: The workflow catches listWorkflowRuns errors and sets trendLine = `Trend: ⊘ Could not fetch prior-day data (${e.message})`.
  • Source-of-truth review needed: .github/workflows/nightly-e2e.yaml legacy Slack attachment wrapper: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: The Slack post step comments Legacy attachment wrapper enables the coloured left-edge bar and constructs payload.attachments[0].
  • Source-of-truth review needed: scripts/scorecard/build-slack-blocks.ts fallback title and no-header behavior: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: buildFallbackText() returns the title and tests assert blocks.some((block) => block.type === 'header') is false.
  • Source-of-truth review needed: scripts/scorecard/build-slack-blocks.ts failed-job URL fallback rendering: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: buildBlocks() renders a code-formatted job name when job.url is null; the test fixture comments url=null exercises fallback rendering.
  • Untrusted workflow_dispatch input can reach the Slack webhook secret boundary (.github/workflows/nightly-e2e.yaml:2281): The generate step still interpolates the untrusted workflow_dispatch inputs.jobs value directly into JavaScript source. A crafted value containing quotes or newlines can break out of the generated string. The same job checks out workspace code, and the later Slack step maps three Slack webhook secrets into the environment before requiring the checked-out builder, so an injection can tamper with code that later executes in the secret-bearing step.
    • Recommendation: Pass inputs.jobs through an environment variable or JSON-safe serialization, validate it against the dispatchable-job allowlist with length and character limits, and keep the Slack secret-bearing step minimal and trusted. Avoid importing mutable workspace code after webhook secrets are present, or load only trusted inline/pinned code.
    • Evidence: const requestedJobsRaw = isDispatch ? '${{ inputs.jobs }}'.trim() : ''; later the Slack step maps SLACK_WEBHOOK_URL_SITUATION_ROOM, SLACK_WEBHOOK_URL_CI, and SLACK_WEBHOOK_URL_PREVIEW from secrets and require()s scripts/scorecard/build-slack-blocks.ts from GITHUB_WORKSPACE.
  • Slack scorecard does not satisfy issue feat(ci): connect nightly scorecard to Slack channel #2614 literally (.github/workflows/nightly-e2e.yaml:2453): Issue feat(ci): connect nightly scorecard to Slack channel #2614 asks for SLACK_WEBHOOK_URL, daily posting at about 8am ET, a header block with date, top flaky jobs, run counts, perfect count, flaky jobs, trend, a link to Actions, and manual workflow_dispatch Slack posting for testing. The current diff uses three channel-specific webhook secrets, keeps the midnight UTC cron, intentionally omits a header block, renders current failed jobs instead of flaky jobs, has no explicit perfect count, and makes selective-dispatch Slack posting opt-in.
    • Recommendation: Either implement the feat(ci): connect nightly scorecard to Slack channel #2614 clauses literally or update/split the tracked acceptance source with maintainer-confirmed behavior for channel-specific secrets, the situation-room/CI/preview routing model, fallback-title/no-header payload, failed-job links instead of flaky jobs, the desired schedule, and selective-dispatch opt-in posting.
    • Evidence: Workflow cron remains 0 0 * * *; Slack env uses SLACK_WEBHOOK_URL_SITUATION_ROOM, SLACK_WEBHOOK_URL_CI, and SLACK_WEBHOOK_URL_PREVIEW; test/scorecard-blocks.test.ts asserts no header block; buildBlocks() renders failedJobs rather than top flaky jobs.
  • Scorecard checkout lacks job-level contents permission (.github/workflows/nightly-e2e.yaml:2253): The scorecard job declares job-level permissions with only actions: read and then runs actions/checkout to load the scorecard builder. Job-level permissions override the workflow-level contents: read permission, so the new checkout can fail before scorecard generation or Slack posting.
    • Recommendation: Add contents: read to the scorecard job permissions, or avoid checkout in this path by embedding the trusted Slack posting code in the workflow step.
    • Evidence: scorecard.permissions contains actions: read only; the next step uses actions/checkout with sparse-checkout: scripts/scorecard.
  • Slack payload fields are rendered without schema validation, escaping, or URL validation (scripts/scorecard/build-slack-blocks.ts:114): The Slack builder interpolates requested job names, failed job names, failed job URLs, trend text, and run URLs directly into Slack mrkdwn/buttons. SCORECARD_DATA is parsed without runtime schema validation before these fields are rendered. Some values are generated locally, but requested jobs originate from workflow_dispatch input and API error text can enter trendLine.
    • Recommendation: Validate SCORECARD_DATA at runtime, allowlist job names, bound array and string lengths, validate URLs as expected GitHub Actions URLs, reject unknown runMode values, and escape Slack mrkdwn text before rendering.
    • Evidence: buildBlocks() renders job.url, job.name, data.trendLine, and data.runUrl directly; .github/workflows/nightly-e2e.yaml calls JSON.parse(process.env.SCORECARD_DATA) without schema validation; getSlackChannel() routes unknown run modes to preview.
  • Slack webhook failure logging includes the remote response body (.github/workflows/nightly-e2e.yaml:2519): The Slack post step logs the full response body from a secret-authenticated webhook request when Slack returns a non-OK status. Even if Slack normally returns benign text, logging remote response bodies broadens leakage and log-injection exposure.
    • Recommendation: Log bounded, sanitized details such as status and statusText. Avoid printing response bodies from secret-authenticated webhook calls unless they are explicitly known safe and size-limited.
    • Evidence: core.warning(`Slack webhook (${channel}) returned ${resp.status}: ${await resp.text()}`);
  • Slack posting lacks workflow-contract and negative-path coverage (test/scorecard-blocks.test.ts:29): The added tests exercise the pure builder's happy paths and one null-URL fallback, but they do not cover the workflow contract or security-sensitive negative paths. Missing coverage includes safe dispatch-input serialization, requested-job allowlisting, checkout permissions, SCORECARD_DATA validation, Slack routing/gating, attachment wrapper shape, malformed URLs, oversized lists, API failure fallbacks, and non-OK webhook handling.
    • Recommendation: Add YAML/source-shape tests for the workflow and negative builder tests for malicious requested job names, invalid structured data, malformed URLs, overlong lists, invalid run modes, Slack mrkdwn/link injection, and response logging. If posting remains inline in workflow code, assert the secret-boundary contract.
    • Evidence: The test imports only scripts/scorecard/build-slack-blocks.ts; workflow behavior such as post_to_slack, webhook secrets, direct expression interpolation, checkout permissions, payload construction, and response logging is not asserted.
  • Failed-job hyperlinks may silently miss reusable workflow jobs (.github/workflows/nightly-e2e.yaml:2318): The hyperlink lookup maps GitHub Actions job display names to html_url, then looks up entries by workflow needs keys. Reusable workflow job display names can differ from caller job IDs, and the lookup fetches only the first 100 jobs without pagination, so failed jobs may often render unlinked.
    • Recommendation: Verify actual job names returned by listJobsForWorkflowRun for this workflow and map them robustly to needs keys, or preserve job URLs from a workflow-supported source. Add a fixture or workflow-contract test covering reusable workflow names and pagination.
    • Evidence: failedNames are derived from Object.entries(needs), urlByName is built from jobsData.jobs.map((j) => [j.name, j.html_url]), and the API call uses per_page: 100 without pagination.
  • Localized fallback and compatibility paths need source-of-truth documentation and tests (.github/workflows/nightly-e2e.yaml:2501): The PR adds several tolerant or compatibility paths without fully documenting the invalid state handled, source boundary, why the source cannot be fixed here, regression test, or removal condition. This includes API failures becoming warnings/fallback text, missing failed-job URLs becoming null, Slack blocks being wrapped in a legacy attachment for color, and moving the issue-requested header into fallback text while tests assert no header block.
    • Recommendation: Add source-of-truth comments and workflow/source-shape tests that preserve the intended payload contract: API failure behavior, URL-null rendering, payload text, attachments[0].color, attachments[0].blocks, and no-header fallback title. If these choices supersede feat(ci): connect nightly scorecard to Slack channel #2614, update the tracked acceptance source.
    • Evidence: The workflow catches listJobsForWorkflowRun errors and maps missing URLs to null, catches listWorkflowRuns errors into Trend: ⊘ Could not fetch prior-day data (...), comments Legacy attachment wrapper enables the coloured left-edge bar, and constructs payload.attachments[0]; tests assert no header and null-URL fallback rendering.
  • Workflow runtime is not proven to load the TypeScript builder (.github/workflows/nightly-e2e.yaml:2475): The Slack step requires scripts/scorecard/build-slack-blocks.ts directly from actions/github-script. The unit tests run through Vitest, which can transform TypeScript, but they do not prove the GitHub Actions runtime will load this .ts file the same way. The PR body also describes a build-slack-blocks.js file, while the diff adds .ts.
    • Recommendation: Either compile/check in a JavaScript module for the workflow to require, use a runtime explicitly known to support this TypeScript loading mode, or add a workflow/source-shape test documenting the intended loader contract.
    • Evidence: require(path.join(process.env.GITHUB_WORKSPACE, 'scripts/scorecard/build-slack-blocks.ts')); tests use createRequire inside a Vitest test file rather than the actions/github-script runtime.
  • Refresh stale scorecard workflow comments (.github/workflows/nightly-e2e.yaml:2189): The scorecard comments still say the job only runs on schedule and not workflow_dispatch, but the job condition now allows both schedule and workflow_dispatch.
    • Recommendation: Update the nearby workflow comments to match the scheduled, manual full, and selective-dispatch Slack behavior.
    • Evidence: Comment says Only runs on schedule (not workflow_dispatch — that uses report-to-pr), while the job condition is github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'.

Workflow run details

This is an automated advisory review. A human maintainer must make the final merge decision.

@hunglp6d hunglp6d marked this pull request as ready for review May 27, 2026 10:28
@hunglp6d hunglp6d self-assigned this May 27, 2026

@coderabbitai coderabbitai Bot left a comment

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.

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/workflows/nightly-e2e.yaml:
- Around line 2343-2345: The inline comment above the SLACK_WEBHOOK_URL
environment line is stale and misleading; update or remove it so it accurately
reflects current configuration: either delete the three-line promotion note or
reword it to state that the step already uses secrets.SLACK_WEBHOOK_URL
(referring to the SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} line) to
avoid operator confusion.
- Around line 2159-2164: Add with: persist-credentials: false to the "Checkout
scorecard builder" step (the step using
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) so the checkout does
not persist Git credentials; keep the existing sparse-checkout and
sparse-checkout-cone-mode keys and only add the persist-credentials: false entry
under with to make the checkout read-only and reduce token exposure.

In `@test/scorecard-blocks.test.ts`:
- Around line 6-11: Replace the top-level direct require with Node's
createRequire pattern: import { createRequire } from "module" at the top of the
test, call const requireFrom = createRequire(import.meta.url), then load the
CommonJS module with const { buildBlocks, buildFallbackText, getStatusColor } =
requireFrom("../scripts/scorecard/build-slack-blocks.js"); and remove the
eslint-disable comment and the direct require(...) call so the test uses
createRequire(import.meta.url) to import those symbols.
🪄 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: Enterprise

Run ID: 4389e4fb-0835-48ca-9d73-27c499342578

📥 Commits

Reviewing files that changed from the base of the PR and between d312144 and e0f46c7.

📒 Files selected for processing (3)
  • .github/workflows/nightly-e2e.yaml
  • scripts/scorecard/build-slack-blocks.js
  • test/scorecard-blocks.test.ts

Comment thread .github/workflows/nightly-e2e.yaml
Comment thread .github/workflows/nightly-e2e.yaml Outdated
Comment thread test/scorecard-blocks.test.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26507252539
Target ref: feat/ci-send-nightly-scorecard-to-slack
Requested jobs: overlayfs-autofix-e2e
Summary: 1 passed, 0 failed, 0 skipped

Job Result
overlayfs-autofix-e2e ✅ success

@hunglp6d hunglp6d added VRDC Issues and PRs submitted by NVIDIA VRDC test team. E2E labels May 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26566729263
Target ref: feat/ci-send-nightly-scorecard-to-slack
Requested jobs: openclaw-tui-chat-correlation-e2e
Summary: 0 passed, 0 failed, 0 skipped

Job Result
openclaw-tui-chat-correlation-e2e ⚠️ cancelled

@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26566787955
Target ref: feat/ci-send-nightly-scorecard-to-slack
Requested jobs: openclaw-tui-chat-correlation-e2e
Summary: 1 passed, 0 failed, 0 skipped

Job Result
openclaw-tui-chat-correlation-e2e ✅ success

@github-actions

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26567721732
Target ref: feat/ci-send-nightly-scorecard-to-slack
Requested jobs: overlayfs-autofix-e2e
Summary: 1 passed, 0 failed, 0 skipped

Job Result
overlayfs-autofix-e2e ✅ success

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
.github/workflows/nightly-e2e.yaml (1)

2415-2424: 💤 Low value

Consider wrapping fetch in try/catch for network resilience.

If fetch throws due to a network error (timeout, DNS failure), the step fails and marks the job as failed—even though the core scorecard summary was already written. Adding a try/catch ensures transient network issues don't obscure the actual E2E results.

Suggested change
-            const resp = await fetch(webhookUrl, {
-              method: 'POST',
-              headers: { 'Content-Type': 'application/json' },
-              body: JSON.stringify(payload),
-            });
-
-            if (!resp.ok) {
-              core.warning(`Slack webhook (${channel}) returned ${resp.status}: ${await resp.text()}`);
-            } else {
-              core.info(`Scorecard posted to Slack (${channel})`);
-            }
+            try {
+              const resp = await fetch(webhookUrl, {
+                method: 'POST',
+                headers: { 'Content-Type': 'application/json' },
+                body: JSON.stringify(payload),
+              });
+
+              if (!resp.ok) {
+                core.warning(`Slack webhook (${channel}) returned ${resp.status}: ${await resp.text()}`);
+              } else {
+                core.info(`Scorecard posted to Slack (${channel})`);
+              }
+            } catch (err) {
+              core.warning(`Slack webhook (${channel}) failed: ${err.message}`);
+            }
🤖 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/nightly-e2e.yaml around lines 2415 - 2424, The fetch to
webhookUrl that posts payload (resp = await fetch(...)) should be wrapped in a
try/catch to prevent network errors from failing the job; catch any error from
fetch, call core.warning with a clear message including channel and the
error.message (and optionally stack), and then continue so the step does not
hide the actual E2E results; keep the existing resp.ok check inside the try
block and on error fall back to core.warning instead of letting the exception
bubble.
🤖 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.

Nitpick comments:
In @.github/workflows/nightly-e2e.yaml:
- Around line 2415-2424: The fetch to webhookUrl that posts payload (resp =
await fetch(...)) should be wrapped in a try/catch to prevent network errors
from failing the job; catch any error from fetch, call core.warning with a clear
message including channel and the error.message (and optionally stack), and then
continue so the step does not hide the actual E2E results; keep the existing
resp.ok check inside the try block and on error fall back to core.warning
instead of letting the exception bubble.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 815ee20e-dfca-4485-adeb-5f40386a6013

📥 Commits

Reviewing files that changed from the base of the PR and between 839d14e and 66f01a8.

📒 Files selected for processing (3)
  • .github/workflows/nightly-e2e.yaml
  • scripts/scorecard/build-slack-blocks.js
  • test/scorecard-blocks.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/scorecard/build-slack-blocks.js
  • test/scorecard-blocks.test.ts

@hunglp6d hunglp6d requested a review from jyaunches May 28, 2026 10:10
@cv cv added v0.0.56 Release target and removed v0.0.55 labels May 29, 2026

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could spam the Slack channel too much. Let's discuss it first!

@cv cv added v0.0.57 Release target and removed v0.0.56 Release target labels Jun 1, 2026
@cv cv merged commit 798d5a3 into main Jun 2, 2026
27 checks passed
@cv cv deleted the feat/ci-send-nightly-scorecard-to-slack branch June 2, 2026 05:58
@wscurran wscurran added area: e2e End-to-end tests, nightly failures, or validation infrastructure area: integrations Third-party service integration behavior feature PR adds or expands user-visible functionality and removed enhancement: integration labels Jun 3, 2026
cv pushed a commit that referenced this pull request Jun 5, 2026
<!-- markdownlint-disable MD041 -->
## Summary
Polish the nightly E2E scorecard Slack message: rename routing secrets,
inject mode + actor into the title and run-mode line, and switch
failed-job names + counts to the GitHub Actions API for reliable
hyperlinks and accurate listings on re-runs.

## Related Issue
(none — incremental polish on top of #4308)

## Changes
- Rename Slack channel tags + secrets to event-type names
(`SITUATION_ROOM` / `CI` → `DAILY` / `FULLRUN`) (due to just pushing
notification to a unified channel as requirement)
- Append `(by *<actor>*)` to the run-mode line for `Manual full run` /
`Selective dispatch`. Schedule runs skip it (actor reflects file
committer, not trigger).
- Inject mode + actor into the title (top-level fallback text).
- Use `listJobsForWorkflowRun` (paginated) as source of truth for failed
jobs and counts. Strips ` / <suffix>` so reusable-workflow names match
caller IDs and hyperlinks render. Last-write-wins dedup so re-runs
reflect final conclusion. Falls back to `needs` context on API failure.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Verification
<!-- Check each item you ran and confirmed. Leave unchecked items you
skipped. Doc-only changes do not require npm test unless you ran it. -->
- [ ] `npx prek run --all-files` passes
- [ ] `npm test` passes
- [ ] Tests added or updated for new or changed behavior
- [ ] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `npm run docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
<!-- DCO sign-off required by CI. Run: git config user.name && git
config user.email -->
Signed-off-by: Hung Le <hple@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Improved nightly scorecard reliability by deriving a canonical job set
(with API-based fallback) and improving failed-job reporting; scorecard
output now includes the workflow actor.
* Reorganized Slack routing to use separate webhooks for daily vs full
runs while preserving preview gating.

* **New Features**
* Slack notifications can show the workflow initiator for clearer run
context; fallback titles are richer.

* **Tests**
  * Updated tests for new routing and actor-suffix behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: e2e End-to-end tests, nightly failures, or validation infrastructure area: integrations Third-party service integration behavior feature PR adds or expands user-visible functionality v0.0.57 Release target VRDC Issues and PRs submitted by NVIDIA VRDC test team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ci): connect nightly scorecard to Slack channel

4 participants