Skip to content

[automated] fix(ci): Prevent duplicate CI failure cause issues - #19582

Closed
Ankit Jain (radical) wants to merge 9 commits into
microsoft:mainfrom
radical:radical-investigate-ci-cause-duplicates
Closed

[automated] fix(ci): Prevent duplicate CI failure cause issues#19582
Ankit Jain (radical) wants to merge 9 commits into
microsoft:mainfrom
radical:radical-investigate-ci-cause-duplicates

Conversation

@radical

@radical Ankit Jain (radical) commented Aug 21, 2026

Copy link
Copy Markdown
Member

Description

[automated]

Analyze CI Failure could open a new ci-failure-cause issue for a failure that already had a canonical issue. The observed RemoteHost recurrence produced two IDs for the same test:

#18840: remotehost-jsonrpc-auth-timeout
#19143: remotehost-jsonrpcauth-rpc-timeout

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 causeId values 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 causeId is appropriate, how to find an existing ID, how to mint one, and which tests cover both consumers. causeId does 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:

dotnet test --project tests/Infrastructure.Tests/Infrastructure.Tests.csproj --no-launch-profile -- --filter-class "*.AnalyzeCiFailureCauseResolverTests" --filter-class "*.AutoRerunTransientCiFailuresTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
Test run summary: Passed! total: 129, failed: 0

gh aw compile analyze-ci-failure --validate --shellcheck --no-emit
Compiled 1 workflow: 1 succeeded, 0 warnings

Fixes #19578

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

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
Copilot AI balanced review requested due to automatic review settings August 21, 2026 21:35
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19582

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19582"

@github-actions github-actions Bot added the area-engineering-systems infrastructure helix infra engineering repo stuff label Aug 21, 2026

Copilot AI 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.

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.

Comment thread eng/test-retry-patterns.json
Comment thread .github/workflows/analyze-ci-failure-cause-resolver.js Outdated
@github-actions

Copy link
Copy Markdown
Contributor

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
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Ankit Jain (radical) and others added 6 commits August 21, 2026 18:30
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
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
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
Copilot AI review requested due to automatic review settings August 22, 2026 01:54
Copilot stopped reviewing on behalf of Ankit Jain (radical) due to an error August 22, 2026 02:19

Copilot AI 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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment thread .github/workflows/analyze-ci-failure-cause-resolver.js
Comment thread .github/workflows/analyze-ci-failure-cause-resolver.js Outdated
Comment thread .github/workflows/analyze-ci-failure-cause-resolver.js
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

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
Copilot AI review requested due to automatic review settings August 24, 2026 19:53

Copilot AI 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.

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, but findPriorCauseById later treats that exactly like no match. With prior IDs such as Legacy.Cause and legacy_Cause, a new legacy-cause proposal 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 current DoesNotMatchAmbiguousNormalizedPriorCauseIds test should expect resolver failure.
        if (!priorByNormalizedId.has(normalizedId)) {
            priorByNormalizedId.set(normalizedId, cause);
        } else if (priorByNormalizedId.get(normalizedId)?.id !== cause.id) {
            priorByNormalizedId.set(normalizedId, null);

@radical

Copy link
Copy Markdown
Member Author

[automated] Superseded by the validated native stack #19806: #19455 -> #19804 -> #19805. This PR's canonical failure-cause changes are carried by #19804, stacked above the trust-validation fixes in #19455 and below the reusable automated-issue reconciliation in #19805.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-engineering-systems infrastructure helix infra engineering repo stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[automated] Analyze CI Failure opens duplicates when prior-cause context is truncated

2 participants