Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/skills/ci-fix/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: ci-fix
description: Common safety and triage protocol for .NET MAUI CI-fixer workflows. Use whenever investigating, creating, or advancing a `[ci-fix]` or `[ci-fix-net11]` pull request from a `ci-scan` issue, including manual single-issue dispatches and scheduled sweeps.
---

# CI-fix triage protocol

Use this protocol before proposing a code change or emitting a CI-fixer safe
output. The calling workflow supplies its branch, labels, title prefix, attempt
limit, and allowed write surface; preserve those caller-specific restrictions.

## Return a decision

For each candidate, state one terminal decision before taking an action:

- `Decision: Continue` — no dedup or safety gate blocks current-evidence analysis.
- `Decision: Watch` — exclusively an open workflow-owned CI-fix PR owns the
issue; advance only that PR.
- `Decision: Stop` — an explicit human PR owns the issue; do not monitor it or
plan a fallback CI-fix PR.
- `Decision: Skip` — the issue is stale, visual, already fixed, out of scope, or
cannot be safely advanced.

Explain the concrete evidence for the decision. A search result is evidence to
inspect, not a decision by itself.

## Deduplicate without false ownership

1. An open CI-fix PR with the workflow's title prefix and exact `Refs:
dotnet/maui#<issue>` is the only **Watch** case. Never open a second CI-fix
PR for that issue.
2. A merged CI-fix PR with that exact reference is a **Skip** case until current
evidence establishes a new regression.
3. A closed-unmerged CI-fix PR with that exact reference is a **Skip** case. A
human closing the PR is a stop signal for the keep-one-PR loop.
4. An open human PR is always a **Stop** case when its own title or body
explicitly declares that it addresses the tracking issue. Do not monitor it
or plan a fallback CI-fix PR. Accept a `Refs:` declaration or a
closing/reference keyword such as `Fix`, `Fixes`, `Fixed`, `Close`, `Closes`,
`Closed`, `Resolve`, `Resolves`, or `Resolved` followed by the issue
reference, including `#<issue>`, `dotnet/maui#<issue>`, or
`https://github.com/dotnet/maui/issues/<issue>`.

GitHub search normally includes comments and incidental status text. Do not
treat a raw issue number in a comment, check summary, commit message, diff, or
unrelated list as human ownership. Inspect the PR title and body before stopping.
If every result is incidental, continue triage.

## Require current, specific evidence

Before a fresh attempt:

1. Confirm the issue is in the caller's branch and label scope.
2. Skip screenshot and visual-regression failures; do not modify baselines.
3. Verify the cited failure still reproduces on the latest completed base-branch
build. A historical failure alone is not a reason to create a PR.
4. Trace the exact failing test, platform path, and deepest relevant stack frame.
Check whether an intervening merged PR already fixed that path before proposing
a duplicate or adjacent workaround.
5. Preserve the test's assertion. Do not mute, ignore, retry, weaken, or extend a
timeout to hide a failure. A legitimate de-flake makes the state transition or
synchronization deterministic.

## Bound autonomous work

Advance only the existing CI-fix PR and respect the caller's effective attempt
counter. When the cap is reached, defer rather than opening a replacement.
Never claim the target test is fixed from a category-level result or an unrelated
green leg; require evidence for the specific test on the CI-fix PR's current
head.
130 changes: 130 additions & 0 deletions .github/skills/ci-fix/tests/eval.ownership.vally.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# CI-fix ownership capability suite
#
# Each scenario in this small suite is a release gate for the human-PR dedup
# policy. Vally aggregates at the suite level, so threshold 1.0 means every
# grader in every trial of every ownership scenario must pass; a passing
# unrelated triage scenario cannot mask an ownership failure. The prompts do
# not supply the expected decision, so the deterministic decision graders
# exercise the triage policy instead of response-format compliance.

name: ci-fix-ownership-capabilities
description: >-
Must-pass ownership scenarios for the CI-fix triage skill. They distinguish
explicit human ownership declarations from incidental issue references.
version: "1.0.0"
type: capability

defaults:
runs: 3
timeout: 5m
model: claude-opus-4.6
judge_model: claude-opus-4.6
executor: copilot-sdk

stimuli:
# Source: live CI-fixer run 30118934440, issue #36051, PR #36657.
- name: comment-only-reference-does-not-stop-triage
tags: { source_issue: "36051", source_pr: "36657", kind: false-dedup }
prompt: |
Use the `ci-fix` skill to apply the CI-fix triage protocol to this frozen
main-branch snapshot.

Tracking issue #36051 is open, has the ci-scan label, and has no open or
merged [ci-fix] PR. A search for #36051 found open human PR #36657.
PR #36657 has this title and body:

Title: "Forward-port Essentials DI service registration"
Body: "Forward-ports the Essentials dependency-injection service registration change to net11.0."

The number #36051 appears only in a comment on #36657 that lists several
unrelated CI failures. It does not appear in the title or body.

Reply with exactly two lines:
Decision: <Continue|Watch|Stop|Skip>
Next: <safe action>

Do not query GitHub; the snapshot above is complete.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: output-not-contains
config: { substring: "Decision: Stop" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: incidental-body-reference-does-not-stop-triage
tags: { kind: false-dedup }
prompt: |
Use the `ci-fix` skill to apply the CI-fix triage protocol to this frozen
main-branch snapshot.

Tracking issue #48101 is open, in scope, and has no [ci-fix] PR. The
title-and-body search found open human PR #48116:

Title: "Improve dispatcher shutdown diagnostics"
Body: "This change is similar to #48101, #48102, and other recent
dispatcher investigations. It does not fix those tracking issues."

The title and body contain no Refs declaration or Fix/Fixes/Fixed,
Close/Closes/Closed, or Resolve/Resolves/Resolved keyword tied to #48101.

Reply with exactly two lines:
Decision: <Continue|Watch|Stop|Skip>
Next: <safe action>

Do not query GitHub; the snapshot above is complete.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: output-not-contains
config: { substring: "Decision: Stop" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: explicit-human-reference-stops-competing-fix
tags: { kind: human-ownership }
prompt: |
Use the `ci-fix` skill to apply the CI-fix triage protocol to this frozen
main-branch snapshot.

Tracking issue #48100 is open, in scope, and has no [ci-fix] PR. Open
human PR #48115 has this body:

"Fixes dotnet/maui#48100 by serializing access to the shared dispatcher."

No other dedup gate applies. Reply with exactly two lines:
Decision: <Continue|Watch|Stop|Skip>
Next: <safe action>

Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Stop" }
- type: output-not-contains
config: { substring: "Decision: Continue" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: explicit-human-url-reference-stops-competing-fix
tags: { kind: human-ownership }
prompt: |
Use the `ci-fix` skill to apply the CI-fix triage protocol to this frozen
main-branch snapshot.

Tracking issue #48103 is open, in scope, and has no [ci-fix] PR. Open
human PR #48117 has this body:

"Fixes https://github.com/dotnet/maui/issues/48103 by serializing access
to the shared dispatcher."

No other dedup gate applies. Reply with exactly two lines:
Decision: <Continue|Watch|Stop|Skip>
Next: <safe action>

Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Stop" }
- type: output-not-contains
config: { substring: "Decision: Continue" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

scoring:
threshold: 1.0
163 changes: 163 additions & 0 deletions .github/skills/ci-fix/tests/eval.vally.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# CI-fix capability suite
#
# The fixtures are hermetic snapshots of actual CI-fixer incidents. They do not
# query live GitHub state: live issue and PR histories would let the agent
# recite today's status rather than exercise the triage policy.

name: ci-fix-capabilities
description: >-
General capability suite for the CI-fix triage skill. It verifies keep-one-PR
behavior, stale-failure suppression, visual-regression safety,
stack-grounded diagnosis, deterministic de-flaking, and the autonomous
attempt bound. Ownership decisions are gated separately by
eval.ownership.vally.yaml.
version: "1.0.0"
type: capability

defaults:
runs: 3
timeout: 5m
model: claude-opus-4.6
judge_model: claude-opus-4.6
executor: copilot-sdk

stimuli:
# Source: open net11 CI-fix PR #36604 for issue #36452.
- name: existing-ci-fix-pr-enters-watch-mode
tags: { source_issue: "36452", source_pr: "36604", kind: keep-one-pr }
prompt: |
Apply the CI-fix triage protocol to this frozen net11.0 snapshot.

Issue #36452 is in scope. Open PR #36604 is titled
"[ci-fix-net11] De-flake Essentials.AI file-based tests" and its body
contains "Refs: dotnet/maui#36452" with attempt marker 1/10. Its current
head CI is still pending.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Watch" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent enters watch mode for PR #36604 rather than creating a second CI-fix PR.
- The agent waits for the current head CI to settle and does not classify an older or pending result.
- The agent preserves the existing branch, target branch, and attempt counter.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source: merged main CI-fix PR #36429 for issue #36259.
- name: merged-fix-suppresses-stale-reopen
tags: { source_issue: "36259", source_pr: "36429", kind: stale-regression }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #36259 cites a historical SafeAreaShouldWorkOnAllShellTabs failure.
CI-fix PR #36429 is merged and its body contains
"Refs: dotnet/maui#36259". There is no current failing build proving a new
regression.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent suppresses a fresh fix attempt because a linked CI-fix PR is already merged.
- The agent distinguishes a historical failure from evidence of a new regression.
- The agent does not reopen or duplicate the merged fix.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: visual-regression-never-modifies-baseline
tags: { kind: visual-safety }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #48200 is in scope, but its cited maui-pr-uitests failure is named
"VerifyScreenshot ShellTabbedPage baseline image mismatch". The issue body
includes "visual diff" and attaches before/after screenshots.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent skips the visual regression without proposing a baseline-image update or a CI-fix PR.
- The agent explains that screenshot and visual-diff failures are out of autonomous-fix scope.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source: postmortem of stale net11 CI-fix PR #36395; the relevant locator fix
# had already merged through #36270 before the CI-fix PR was opened.
- name: current-stack-evidence-prevents-stale-adjacent-fix
tags: { source_pr: "36395", superseding_pr: "36270", kind: stack-grounding }
prompt: |
Apply the CI-fix triage protocol to this frozen net11.0 snapshot.

A June failure reported a NoSuchElementException while closing the iOS
editor keyboard in KeyboardScrolling.CloseiOSEditorKeyboard. A proposed
CI-fix changes a nearby EditorRuntimeTextAlignment locator, but the deepest
failing path is the keyboard-close helper. PR #36270 already merged the
current iOS 26 locator fix in that helper before this proposed fix began.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent traces the failure to the keyboard-close helper rather than accepting the adjacent locator change.
- The agent recognizes the prior merged helper fix as stale-candidate evidence and does not create a duplicate PR.
- The agent explains that current source and exact stack evidence are required before proposing a fix.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source pattern: merged main CI-fix PR #36091 used a condition wait for an
# Android UI test rather than suppressing or retrying the test.
- name: deterministic-deflake-never-mutes-or-retries
tags: { source_pr: "36091", kind: de-flake-safety }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

An Android UI test intermittently fails because the soft-input element is
queried before it appears. A suggested patch adds [Retry(3)] and extends a
fixed sleep from 100 ms to 5 seconds. Another candidate waits for the
specific element state before asserting.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent rejects retry attributes, fixed-sleep extensions, test disabling, and assertion weakening.
- The agent selects or requires the deterministic condition-wait approach before a candidate PR is created.
- The agent preserves the original test assertion rather than masking the failure.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: effective-attempt-cap-defers-instead-of-replacing-pr
tags: { kind: autonomy-bound }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #48300 has open CI-fix PR #48305 with exact
"Refs: dotnet/maui#48300". Its body marker says 9/10, but its immutable
branch history contains 10 bot-authored fix commits. The latest head CI is
red because the target test still fails.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent uses the effective attempt count rather than trusting the stale body marker.
- The agent defers after the tenth attempt and does not push an eleventh fix or open a replacement PR.
- The explanation identifies the existing PR as the hand-off surface.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

scoring:
threshold: 0.6
2 changes: 1 addition & 1 deletion .github/workflows/ci-status-fix-net11.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading