Fix main CI failure attribution and classification - #19455
Fix main CI failure attribution and classification#19455Ankit Jain (radical) with Copilot wants to merge 28 commits into
Conversation
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (3)
.github/workflows/analyze-ci-failure.md:169
- The main-run context records only each candidate PR's title/URL and then deletes the comparison payload. Because the agent is explicitly barred from querying GitHub, it receives no changed-file or patch data for these merges and cannot verify the semantic conflicts this change is intended to diagnose. Preserve and render the comparison's changed files/patches (or collect equivalent per-PR change data) so attribution can be based on code evidence rather than titles.
'. + [$commit + {pull_request: {
number: $pr.number,
title: $pr.title,
url: $pr.html_url,
merged_at: $pr.merged_at
}}]' ci-failure-data/candidate-merges.json \
.github/workflows/analyze-ci-failure.md:746
- For a main run this row contains
main, but reused cause issues created by the old workflow still have aPRcolumn header. The existing-issue path only appends this row and never migrates that header, so stable infra/flaky issues will misleadingly displaymainas a PR value. Rewrite the legacy header toContextbefore appending a main occurrence.
if [ "$RUN_SCOPE" = "main" ]; then
OCCURRENCE_CONTEXT="main"
else
OCCURRENCE_CONTEXT="#${PR_NUMBER}"
fi
NEW_OCCURRENCE_ROW="| ${OCC_DATE} | [${RUN_ID}](${RUN_URL}) | ${FIRST_JOB} | ${OCCURRENCE_CONTEXT} |"
.github/workflows/analyze-ci-failure.md:1189
- The schema now permits
main-repository-breakagefor jobs, but deterministic failed tests on main still have onlyflaky | code-issueavailable in both the JSON example andfailed_tests[].classificationdetails. That forces a main test regression to be mislabeled as a PR code issue or omitted. Addmain-repository-breakageto the failed-test classification contract and example as well.
- `failed_jobs[].classification`: Per-job classification — one of `"transient-infra"`, `"flaky-test"`, `"code-issue"`, or `"main-repository-breakage"`.
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19455Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19455" |
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
fab07dc to
b2e5215
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
Suppressed comments (2)
.github/workflows/analyze-ci-failure.md:783
TRUSTED_PR_NUMBERSis used for the final PR comment, but the cause persistence and issue-occurrence paths below still read.pr.numberfrom the agent-authored analysis (lines 836 and 878). A mismatched agent PR number can therefore still be written to the memory branch and attributed in a cause issue, which leaves the trust-boundary problem this change is intended to close. Derive all occurrence PR metadata from this trusted value instead.
TRUSTED_PR_NUMBERS=$(jq -r '.pr_numbers' "$RUN_CONTEXT_FILE")
.github/workflows/analyze-ci-failure.md:990
- The main-breakage issue still publishes agent-authored
main_contextandtriggering_merge_prvalues without comparing them to the downloaded trusted artifacts. An incorrect analysis can therefore put the wrong failed SHA or triggering PR into the privileged issue side effect. Read these fields directly fromrun-context.json,last-successful-main-run.json, andtriggering-merge-pr.json.
LAST_SUCCESSFUL_SHA=$(jq -r '.main_context.last_successful_main_sha // "unknown"' "$ANALYSIS_FILE")
FAILED_SHA=$(jq -r '.main_context.failed_sha // "unknown"' "$ANALYSIS_FILE")
TRIGGERING_MERGE=$(jq -r 'if .triggering_merge_pr then "#\(.triggering_merge_pr.number) \(.triggering_merge_pr.title)" else "Not found" end' "$ANALYSIS_FILE")
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved attribution, pagination, and workflow-command injection issues must be fixed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
.github/workflows/analyze-ci-failure-validation.sh:170
- A cause filename is agent-controlled, but it is interpolated into a workflow-command line before the slug check at line 206. A filename containing a newline (for example,
bad\n::warning::...json) emits a second runner command during validation. Validate the basename before any diagnostic uses it (and format the rejection with%q) so malformed agent output cannot forge Actions annotations.
for CAUSE_FILE in "${CAUSE_FILES[@]}"; do
if ! jq empty "$CAUSE_FILE" 2>/dev/null; then
echo "::error::Invalid JSON in cause file: $(basename "$CAUSE_FILE")"
.github/workflows/analyze-ci-failure-validation.sh:219
CAUSE_TYPEis also agent-controlled and is printed raw inside an Actions workflow command on the rejection path. A value containing a newline can forge a following runner command even though validation ultimately fails. Render the rejected value with a single-line-safe representation such as Bash%q.
echo "::error::Cause ${CAUSE_BASENAME} type '${CAUSE_TYPE}' is not permitted for run scope ${TRUSTED_RUN_SCOPE}"
.github/workflows/analyze-ci-failure-validation.sh:255
- Prior-cause records are explicitly treated as untrusted historical data, but their raw
typeis interpolated into a runner command here. A malformed stored value containing a newline can inject a second workflow command before this fail-closed exit. Avoid including the raw value in::error::output or escape it first.
PRIOR_CAUSE_TYPE=$(jq -r '.type // ""' "$PRIOR_CAUSE_FILE")
if [ "$PRIOR_CAUSE_TYPE" != "$CAUSE_TYPE" ]; then
echo "::error::Cause ${CAUSE_BASENAME} cannot change type from '${PRIOR_CAUSE_TYPE}' to '${CAUSE_TYPE}'"
- Files reviewed: 9/10 changed files
- Comments generated: 5
- Review effort level: Balanced
The CI-failure workflow could accept incomplete history or arbitrary PR associations when GitHub returned partial, stale, or ambiguous data. Rejected agent values could also inject additional Actions workflow commands through newline-bearing diagnostics. Drive workflow-run pagination from returned page sizes and fail closed when fewer unique runs arrive than GitHub advertised. Require unique commit-to-PR associations, include closed fork PRs through a fully paginated exact-SHA lookup, and render rejected untrusted values with Bash %q before emitting workflow commands. Add regressions for incomplete and stale pagination, ambiguous associations, closed-PR lookup, and newline-bearing diagnostic values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
|
[automated] Addressed the three suppressed workflow-command findings in
The regressions assert that newline-bearing rejected values produce exactly one Actions workflow-command line. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Critical validation and moderate fail-closed attribution and publication issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
.github/workflows/analyze-ci-failure.md:540
- When candidate history is
unavailableorincomplete, this still exposes the triggering PR to the agent. That contradicts the PR's fail-closed guarantee to withhold triggering-merge context unless every commit association is complete, and the validator does not prevent the agent from copying that PR into a published cause title or diagnostic. Read the history state before this section and render the triggering merge only when it isavailable.
echo "Triggering merge PR (context only, not necessarily causal):"
echo ""
bash .github/workflows/analyze-ci-failure-persistence.sh \
render-untrusted-json ci-failure-data/triggering-merge-pr.json
.github/workflows/analyze-ci-failure.md:562
- The candidate script intentionally preserves partial results while setting the state to
incomplete, but this unconditional length check still sends those partial candidates to the agent. This defeats the stated fail-closed behavior and can lead to incomplete PR attribution in published cause text. Only render candidates when the history state isavailable.
if [ "$(jq 'length' ci-failure-data/candidate-merges.json)" -gt 0 ]; then
echo ""
bash .github/workflows/analyze-ci-failure-persistence.sh \
render-untrusted-json ci-failure-data/candidate-merges.json
fi
- Files reviewed: 9/10 changed files
- Comments generated: 3
- Review effort level: Balanced
CI analysis could publish agent-supplied test diagnostics and expose main attribution even when candidate history was incomplete. Duplicate TRX records and raw job names also made the trust boundary inconsistent. Normalize trusted failed jobs and TRX failures before validation. Require reported tests and flaky causes to match trusted evidence, rebuild diagnostics from that evidence, and withhold merge context unless history is complete. Identical cross-platform TRX records collapse; conflicting records fail closed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
|
[automated] Addressed the two suppressed Copilot findings in 362864b:
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
One critical and four moderate findings remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 9/10 changed files
- Comments generated: 5
- Review effort level: Balanced
The analyzer could download and expand an oversized test-results artifact, misread paginated annotations, and persist flaky-test causes against the wrong failed job. Recreated cause issues also reset occurrence totals, while runs without a trusted subject PR could fail on a comment that would never be published. Select only bounded canonical artifacts, verify downloaded size, and stream safe TRX entries under entry and byte limits. Flatten annotation pages, bind flaky causes to exact trusted test/job evidence, preserve stored occurrence totals, and budget comments only when publication is possible. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🔵 Needs a closer look
Exact trusted test-to-job attribution is not currently enforced.
Review details
Suppressed comments (1)
.github/workflows/analyze-ci-failure-validation.sh:143
- The trusted test and job are validated independently here:
$matchesis selected only by test name, while.jobmerely has to equal any failed-job name. The collectedtest-failures.jsonrecords contain no job identity, so a real TRX failure can be assigned to a different failed job and then accepted as that job's flaky cause. Preserve job identity while collecting each TRX record and require one trusted{test, job}match before binding diagnostics; otherwise the claimed exact test/job attribution is not enforced.
all(.failed_tests[];
. as $reported |
([$trusted_tests[0][] | select(.test == $reported.name)]) as $matches |
($matches | length) == 1 and
any($trusted_jobs[0][]; .name == $reported.job))
- Files reviewed: 9/10 changed files
- Comments generated: 0 new
- Review effort level: Balanced
The CI failure analyzer treated trusted TRX test names and failed job names as independent sets. A model could therefore associate a real test failure from one job with another failed job, corrupting flaky-test attribution and rerun decisions. Select each failed test job's workflow-defined logs artifact within the analyzed attempt and parse it in isolation under that API job name. Require validation and diagnostic rebinding to match the same trusted test and job pair, while preserving unavailable evidence when collection fails. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Incomplete trusted evidence can still permit incorrect publication or reruns, and recurring flaky-cause identity is insufficiently validated.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
.github/workflows/analyze-ci-failure-validation.sh:125
- Trusted TRX evidence is loaded only when the agent reports at least one failed test. If the agent omits all—or only some—trusted failures, validation accepts the result; for example, a
transient-infraanalysis withfailed_tests: []passes even whenci-failure-data/test-failures.jsoncontains a failed test. The rerun handler likewise checks only the agent array, so this can publish or automatically rerun a real test failure as infrastructure. Sanitize trusted evidence unconditionally and require the reported{name, job}set to equal the trusted{test, job}set before either side effect path proceeds.
FAILED_TEST_COUNT=$(jq '[.failed_tests[]?] | length' "$ANALYSIS_FILE")
if [ "$FAILED_TEST_COUNT" -gt 0 ]; then
- Files reviewed: 9/10 changed files
- Comments generated: 3
- Review effort level: Balanced
Document the analyzer's attribution model, trust boundary, per-job test evidence, and side-effect gates. Cross-link it from the separate red-main reporter documentation so the implementation detail removed from the PR description remains discoverable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Missing artifact and test-evidence checks can permit unsafe classification or reruns, and the main rerun contract is inconsistent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
.github/workflows/analyze-ci-failure-validation.sh:125
- The trusted-test comparison runs only when the agent reports at least one test. An analysis with
failed_tests: []therefore passes even whenci-failure-data/test-failures.jsoncontains a real TRX failure (the validator exits 0 for that fixture), allowing the rerun path to treat a test failure as pure infrastructure. Inspect trusted test evidence independently of the agent array and reject atransient-infraanalysis/rerun whenever trusted failed tests were omitted.
FAILED_TEST_COUNT=$(jq '[.failed_tests[]?] | length' "$ANALYSIS_FILE")
if [ "$FAILED_TEST_COUNT" -gt 0 ]; then
- Files reviewed: 11/12 changed files
- Comments generated: 2
- Review effort level: Balanced
CI analysis could treat missing, malformed, or partial TRX evidence as an empty successful result. That allowed failed tests to be omitted from an analysis or transient jobs to be rerun without complete evidence. Reused flaky cause IDs could also retain occurrences for a different test. Represent test evidence as complete, unavailable, or not applicable. Require every expected artifact to download, extract, and parse before accepting exact unique test/job pairs. Reruns now use the trusted evidence state and failed-test file, and flaky causes preserve their stored test identity through publication. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
|
[automated] Addressed in f32078f: trusted test evidence is now loaded independently of the agent's |
This comment has been minimized.
This comment has been minimized.
Bring the active PR branch up to date without rewriting its existing stacked history. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
There was a problem hiding this comment.
🟡 Changes recommended
Three moderate fail-closed validation and PR-state issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/12 changed files
- Comments generated: 3
- Review effort level: Balanced
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
Empty TRX evidence can be accepted, and PR comments can still be published to closed pull requests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
.github/workflows/analyze-ci-failure-persistence.sh:145
- A selected logs artifact can contain only binlogs/logs and no TRX file (for example, when a test job fails before producing results). In that case the loop is empty,
jq -scwrites[], and collection marks the evidencecomplete, allowing publication or rerun without any test evidence. Fail this command when no TRX file is present so the caller keeps the evidence unavailable.
json_lines=$(mktemp)
- Files reviewed: 10/12 changed files
- Comments generated: 1
- Review effort level: Balanced
CI failure analysis could treat malformed GitHub cardinality metadata and test-result artifacts without TRX files as complete evidence. It could also comment on a closed PR because the final gate checked only the lock state. Validate comparison and workflow-run response shapes before attribution, reject selected artifacts that contain no TRX files, and require a live open/unlocked PR immediately before commenting. Run-scoped persistence remains available when the PR is not actionable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b364edcb-a6a1-48a6-aedb-011cda75c167
Tests selector1 / 99 PR test projects · 0 PR jobs · 0 advisory-only targets, from 10 changed files. Selected PR test projects (1 / 99)
Selected PR jobs (0)none Advisory workflow impact (0)none How these were chosen — grouped by what changed📄 📄 📄 📄 📄 📄 📄 📄 🧪 🧪 Job reasonsnone Selection computed for commit |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved attribution and validation flaws can blame unrelated PRs and omit flaky failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/12 changed files
- Comments generated: 3
- Review effort level: Balanced
| (.[0].total_commits | type) == "number" and | ||
| .[0].total_commits >= 0 and | ||
| .[0].total_commits == (.[0].total_commits | floor) and | ||
| all(.[]; type == "object" and (.commits | type) == "array") and |
| echo "## Description" | ||
| echo "" | ||
| echo "$TITLE_CODE" | ||
| echo "" | ||
| echo "**Type**: ${CAUSE_TYPE}" |
| if [ "${#CAUSE_FILES[@]}" -ne 0 ]; then | ||
| for CAUSE_FILE in "${CAUSE_FILES[@]}"; do | ||
| if [ "$(jq -r '.type // ""' "$CAUSE_FILE")" = "flaky-test" ]; then | ||
| CAUSE_TEST_NAME=$(jq -r '.test_name // ""' "$CAUSE_FILE") | ||
| if ! jq -e --arg test_name "$CAUSE_TEST_NAME" ' | ||
| any(.failed_tests[]; | ||
| .classification == "flaky" and .name == $test_name) | ||
| ' "$ANALYSIS_FILE" >/dev/null; then |
What broke
Failed
pushruns onmaincould be blamed on the pull request that happened to trigger the build, even when the failure came from an earlier merge or the combined repository state.The motivating failure was a semantic merge conflict on
main:PR #19090 triggered the build, but the break came from the interaction between the earlier merges of #19084 and #18976.
Root cause
The workflow inferred attribution from associated pull requests instead of the failed run's immutable metadata. It also allowed incomplete history, ambiguous associations, and partial test evidence to reach downstream actions.
How this fixes it
The analyzer now derives run identity and candidate attribution from trusted GitHub metadata. Pull-request runs require one unambiguous subject PR; failed
mainruns consider every merge since the last successful run and withhold attribution when history is incomplete.Agent output remains a proposal. Before any side effect, deterministic validation requires exact run and failed-job identity, complete test evidence with exact unique
{test, job}pairs, compatible causes, and a valid rerun request. The operational contract is documented indocs/ci/analyze-ci-failure.md.Important callouts
tests.ymlandrun-tests.ymlnaming contract and remains unavailable when expected artifacts are missing, ambiguous, malformed, incomplete, or contain no TRX files.Testing
AnalyzeCiFailureWorkflowTestscontains 243 passing tests covering attribution, evidence completeness, exact test/job binding, cause identity, publication, persistence, and rerun gates.Checklist
Fixes #19454