This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Post-trigger check new failing tests | |
| on: | |
| push: | |
| branches: | |
| - post_trigger_check_new_failing_tests | |
| workflow_dispatch: | |
| inputs: | |
| source_workflow_run_id: | |
| description: 'Workflow run ID to re-analyze (where check_new_failures failed/was skipped)' | |
| required: true | |
| type: string | |
| job_name: | |
| description: 'CI suite name as shown in GitHub UI, e.g. "Quantization CI", "Model CI"' | |
| required: true | |
| type: string | |
| only_process_job: | |
| description: 'Skip "Find commits" jobs and only re-run the process job (use when find-commits already ran successfully but the process step failed)' | |
| required: false | |
| type: boolean | |
| default: false | |
| # For `push` runs: set these values directly to test without workflow_dispatch | |
| env: | |
| source_workflow_run_id: "25356138915" | |
| job_name: "Model CI" | |
| only_process_job: "false" | |
| jobs: | |
| prepare: | |
| name: Fetch inputs from source run | |
| runs-on: ubuntu-22.04 | |
| outputs: | |
| source_workflow_run_id: ${{ steps.resolve.outputs.source_workflow_run_id }} | |
| only_process_job: ${{ steps.resolve.outputs.only_process_job }} | |
| docker: ${{ steps.fetch-inputs.outputs.docker }} | |
| job: ${{ steps.fetch-inputs.outputs.job }} | |
| slack_report_channel: ${{ steps.fetch-inputs.outputs.slack_report_channel }} | |
| ci_event: ${{ steps.fetch-inputs.outputs.ci_event }} | |
| report_repo_id: ${{ steps.fetch-inputs.outputs.report_repo_id }} | |
| commit_sha: ${{ steps.fetch-inputs.outputs.commit_sha }} | |
| pr_number: ${{ steps.fetch-inputs.outputs.pr_number }} | |
| max_num_runners: ${{ steps.fetch-inputs.outputs.max_num_runners }} | |
| steps: | |
| - name: Resolve inputs | |
| id: resolve | |
| env: | |
| source_workflow_run_id: ${{ inputs.source_workflow_run_id || env.source_workflow_run_id }} | |
| only_process_job: ${{ inputs.only_process_job || env.only_process_job }} | |
| run: | | |
| echo "source_workflow_run_id=$source_workflow_run_id" >> $GITHUB_OUTPUT | |
| echo "only_process_job=$only_process_job" >> $GITHUB_OUTPUT | |
| - name: Fetch inputs from source run job log | |
| id: fetch-inputs | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const sourceRunId = parseInt('${{ inputs.source_workflow_run_id || env.source_workflow_run_id }}'); | |
| const jobName = `${{ inputs.job_name || env.job_name }}`; | |
| const targetJobName = `${jobName} / Check new failures / Setup matrix for finding commits`; | |
| // Find the job (paginate in case there are many jobs in the run) | |
| let targetJobId = null; | |
| let page = 1; | |
| while (true) { | |
| const { data } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: sourceRunId, | |
| per_page: 100, | |
| page: page, | |
| }); | |
| const job = data.jobs.find(j => j.name === targetJobName); | |
| if (job) { | |
| targetJobId = job.id; | |
| break; | |
| } | |
| if (data.jobs.length < 100) break; | |
| page++; | |
| } | |
| if (!targetJobId) { | |
| core.setFailed(`Job "${targetJobName}" not found in run ${sourceRunId}. Check that job_name matches the first segment of the full job name in the GitHub UI.`); | |
| return; | |
| } | |
| // Download the job log (GitHub returns a redirect which Octokit follows) | |
| const logResponse = await github.rest.actions.downloadJobLogsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| job_id: targetJobId, | |
| }); | |
| const logText = logResponse.data; | |
| const parsedInputs = {}; | |
| let inInputs = false; | |
| for (const line of logText.split('\n')) { | |
| // Strip the timestamp prefix: "2026-05-05T06:02:50.3702012Z " | |
| const content = line.replace(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z /, ''); | |
| if (content.includes('##[group]Inputs') || content.includes('##[group] Inputs')) { | |
| inInputs = true; | |
| continue; | |
| } | |
| if (inInputs) { | |
| if (content.includes('##[endgroup]')) break; | |
| // Each input line is " key: value" (2-space indent) | |
| const match = content.match(/^ (\w+): ?(.*)/); | |
| if (match) parsedInputs[match[1]] = match[2].trim(); | |
| } | |
| } | |
| if (Object.keys(parsedInputs).length === 0) { | |
| core.setFailed('Could not parse any inputs from the job log. The ##[group] Inputs section may be missing or in an unexpected format.'); | |
| return; | |
| } | |
| console.log('Extracted inputs:'); | |
| for (const [key, value] of Object.entries(parsedInputs)) { | |
| console.log(` ${key}: ${value}`); | |
| core.setOutput(key, value); | |
| } | |
| - name: Fetch END_SHA from source run job log | |
| id: fetch-end-sha | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| const sourceRunId = parseInt('${{ steps.resolve.outputs.source_workflow_run_id }}'); | |
| const jobName = `${{ inputs.job_name || env.job_name }}`; | |
| const targetJobPrefix = `${jobName} / Check new failures / Find commits for new failing tests`; | |
| // Find any matrix instance — they all share the same END_SHA | |
| let targetJobId = null; | |
| let page = 1; | |
| while (true) { | |
| const { data } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: sourceRunId, | |
| per_page: 100, | |
| page: page, | |
| }); | |
| const job = data.jobs.find(j => j.name.startsWith(targetJobPrefix)); | |
| if (job) { | |
| targetJobId = job.id; | |
| break; | |
| } | |
| if (data.jobs.length < 100) break; | |
| page++; | |
| } | |
| if (!targetJobId) { | |
| core.warning(`No "Find commits for new failing tests" job found in run ${sourceRunId}. END_SHA will be computed automatically.`); | |
| return; | |
| } | |
| const logResponse = await github.rest.actions.downloadJobLogsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| job_id: targetJobId, | |
| }); | |
| const logText = logResponse.data; | |
| let endSha = null; | |
| let inTargetStep = false; | |
| let inEnvBlock = false; | |
| for (const line of logText.split('\n')) { | |
| const content = line.replace(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z /, ''); | |
| if (content.includes('##[group]') && content.includes('check_bad_commit.py')) { | |
| inTargetStep = true; | |
| continue; | |
| } | |
| if (!inTargetStep) continue; | |
| if (content.includes('##[endgroup]')) break; | |
| if (content.trim() === 'env:') { | |
| inEnvBlock = true; | |
| continue; | |
| } | |
| if (inEnvBlock) { | |
| const match = content.match(/^\s+END_SHA:\s*(.+)/); | |
| if (match) { | |
| endSha = match[1].trim(); | |
| break; | |
| } | |
| } | |
| } | |
| if (endSha) { | |
| console.log(`END_SHA: ${endSha}`); | |
| core.setOutput('end_sha', endSha); | |
| } else { | |
| core.warning('Could not find END_SHA in the "Check failed tests" step env block. END_SHA will be computed automatically.'); | |
| } | |
| - name: Upload END_SHA artifact | |
| if: ${{ steps.fetch-end-sha.outputs.end_sha != '' }} | |
| run: | | |
| mkdir end_sha_override | |
| echo "${{ steps.fetch-end-sha.outputs.end_sha }}" > end_sha_override/end_sha.txt | |
| - uses: actions/upload-artifact@v4 | |
| if: ${{ steps.fetch-end-sha.outputs.end_sha != '' }} | |
| with: | |
| name: end_sha_override | |
| path: end_sha_override/end_sha.txt | |
| check_new_failures: | |
| name: Check new failures | |
| needs: prepare | |
| uses: ./.github/workflows/check_failed_tests.yml | |
| with: | |
| docker: ${{ needs.prepare.outputs.docker }} | |
| job: ${{ needs.prepare.outputs.job }} | |
| slack_report_channel: ${{ needs.prepare.outputs.slack_report_channel }} | |
| ci_event: ${{ needs.prepare.outputs.ci_event }} | |
| report_repo_id: ${{ needs.prepare.outputs.report_repo_id }} | |
| commit_sha: ${{ needs.prepare.outputs.commit_sha }} | |
| pr_number: ${{ needs.prepare.outputs.pr_number }} | |
| max_num_runners: ${{ fromJSON(needs.prepare.outputs.max_num_runners) }} | |
| source_workflow_run_id: ${{ needs.prepare.outputs.source_workflow_run_id }} | |
| only_process_job: ${{ fromJSON(needs.prepare.outputs.only_process_job) }} | |
| secrets: inherit |