[automated] fix(ci): Prevent duplicate CI failure cause issues - #19582
[automated] fix(ci): Prevent duplicate CI failure cause issues#19582Ankit Jain (radical) wants to merge 9 commits into
Conversation
The CI failure analyzer treated model-generated cause slugs as canonical. When prior causes fell outside the prompt excerpt, recurring failures could create duplicate memory records and issues. Occurrences also used the first failed job, attributing multi-job failures to the wrong leg. Resolve proposed causes against complete memory before persistence using canonical test names, retry-pattern cause IDs, aliases, and explicit matchers. Require per-cause job IDs, serialize publication, publish canonical memory before issue side effects, and preserve durable issue links. Add behavioral coverage for the RemoteHost and 0xC0000142 regressions, theory names, aliases, ambiguous matches, same-job isolation, job attribution, and CLI file rewrites. Fixes microsoft#19578 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19582Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19582" |
There was a problem hiding this comment.
Pull request overview
Makes CI failure identity resolution deterministic before publishing, preventing duplicate recurring-cause issues and correcting job attribution.
Changes:
- Adds canonical cause resolution using tests, aliases, retry patterns, and matchers.
- Serializes publishing and preserves durable cause metadata.
- Adds resolver and workflow regression tests.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/analyze-ci-failure.md |
Integrates canonicalization and serialized publishing. |
.github/workflows/analyze-ci-failure.lock.yml |
Regenerates the compiled workflow. |
.github/workflows/analyze-ci-failure-cause-resolver.js |
Implements deterministic cause resolution. |
eng/test-retry-patterns.json |
Associates the Windows failure pattern with a cause ID. |
tests/Infrastructure.Tests/WorkflowScripts/AnalyzeCiFailureCauseResolverTests.cs |
Tests resolver and workflow behavior. |
tests/Infrastructure.Tests/WorkflowScripts/analyze-ci-failure-cause-resolver.harness.js |
Provides the Node test harness. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
The initial canonicalization change allowed `causeId` only in the new resolver, so the existing auto-rerun validator rejected the entire shared config. Attribution also searched every job sharing a normalized test name, while resolver errors aborted the independent PR analysis comment. Align the shared schema, scope attribution to each cause's declared jobs, and fail closed on conflicting identities while preserving explicit aliases. Defer resolver failure until after the PR comment, and make retry matching tolerate disabled or invalid rules. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
CI failure publication could still stop when historical memory contained a pre-contract cause ID, when the agent proposed a recoverably malformed ID, or when a theory argument itself contained parentheses. Normalize current proposals into deterministic slugs while retaining uniquely equivalent legacy identities. Keep ambiguous legacy identities unmatched, preserve workflow-owned issue links, include all validated job-backed causes in the run summary, and align theory-name canonicalization with the repository's test tooling. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
…i-cause-duplicates
Legacy cause IDs could be reused by the resolver but rejected by issue publication, leaving occurrences without a recurring-cause issue. Unsupported stored types could also produce durable references that the publisher intentionally skipped. Migrate legacy records forward to safe deterministic IDs while preserving history, issue links, and aliases. Enforce supported cause types at the resolver boundary and document how contributors should discover, reuse, or mint retry-pattern cause IDs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
Agent-authored cause files could include canonical_id even though that field belongs to stored alias records. normalizeCause spread every proposal field into the canonical record, so the value reached the memory branch and could redirect or break identity resolution on a later run. Remove canonical_id at the publisher trust boundary. Add a regression test that fails when a proposal can persist its own alias target. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
…i-cause-duplicates
Agent-authored test_names entries were not validated against the cause's failed jobs. They could add unrelated failed-test output to matcher evidence and then persist those names as durable test identities, causing future failures to resolve to the wrong canonical cause. Use only the validated singular test_name from current proposals while retaining test aliases already stored in canonical memory. Add a regression test that previously redirected the proposed cause through an unrelated test's matcher. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Malformed stored regex matchers currently abort with raw RegExp errors that do not identify the corrupted cause metadata. Unsafe retry-pattern cause IDs can also become migration IDs and construct paths outside the memory directory. Validate configured cause IDs before matching, report stored matcher failures with cause and matcher context, and reject unsafe migration IDs at the filesystem boundary. Add regressions proving malformed data fails atomically without moving external files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ce089377-1130-4f90-a98e-250324a1b769
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
.github/workflows/analyze-ci-failure-cause-resolver.js:559
- Renaming the legacy record also changes the issue marker from the legacy ID to the normalized ID. If the stored cause lacks
issue_url—for example, issue creation succeeded but the later link push failed—the publisher searches only the new marker and cannot find the already-created issue carrying the legacy marker, so migration creates a duplicate. Preserve the legacy ID as a searchable alias or search legacy markers when resolving the issue link before dropping the old identity.
const cause = JSON.parse(fs.readFileSync(legacyPath, 'utf8'));
cause.id = migration.canonical_id;
// A temporary path is required for case-only renames on case-insensitive file systems.
const temporaryPath = `${canonicalPath}.migrating`;
fs.writeFileSync(temporaryPath, `${JSON.stringify(cause, null, 2)}\n`);
fs.rmSync(legacyPath);
fs.renameSync(temporaryPath, canonicalPath);
.github/workflows/analyze-ci-failure-cause-resolver.js:205
- An ambiguous normalized ID is stored as
null, butfindPriorCauseByIdlater treats that exactly like no match. With prior IDs such asLegacy.Causeandlegacy_Cause, a newlegacy-causeproposal therefore proceeds as a brand-new cause and can create another issue—the opposite of the stated fail-closed behavior. Reject lookup of an ambiguous normalized key instead; the currentDoesNotMatchAmbiguousNormalizedPriorCauseIdstest should expect resolver failure.
if (!priorByNormalizedId.has(normalizedId)) {
priorByNormalizedId.set(normalizedId, cause);
} else if (priorByNormalizedId.get(normalizedId)?.id !== cause.id) {
priorByNormalizedId.set(normalizedId, null);
Description
[automated]
Analyze CI Failurecould open a newci-failure-causeissue for a failure that already had a canonical issue. The observed RemoteHost recurrence produced two IDs for the same test:The publisher treated the model-generated slug as authoritative. When the matching record fell outside the prompt excerpt, the model could propose a new ID and the publisher had no deterministic way to recover the existing identity. Occurrences also used the first failed job globally, which could attribute a cause to the wrong job.
The publisher now resolves every proposed cause against the complete memory branch before persisting data or creating issues. It canonicalizes normalized test identity, optional retry-pattern
causeIdvalues for uniquely diagnostic signals, stored explicit matchers, and aliases. Ambiguous or conflicting matches fail closed.Legacy unsafe IDs are migrated to normalized slugs while preserving occurrences, issue links, and aliases. Agent-controlled fields cannot overwrite workflow-owned identity metadata, and unsupported cause types are rejected before publication.
Each cause must reference its failed job IDs. Publication is serialized, and canonical memory is pushed before issue side effects so the next run observes the established identity.
The retry-pattern documentation now explains when
causeIdis appropriate, how to find an existing ID, how to mint one, and which tests cover both consumers.causeIddoes not affect retry matching; it only gives failure analysis a stable identity for a uniquely diagnostic retry signal.The workflow-wide concurrency currently serializes analysis as well as publication because the gh-aw safe-job schema cannot express publication-only concurrency.
Validation:
Fixes #19578
Checklist
<remarks />and<code />elements on your triple slash comments?