You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6f1d933
Browse filesBrowse the repository at this point in the historyBrowse files
[aw] Make CI KBE issue search deterministic (#133684)
## Summary
- Route CI failure scanner issue searches through a repository-scoped
MCP script that returns only issue numbers and author logins.
- Require every returned candidate to be inspected with `issue_read`
before making semantic duplicate decisions.
- Remove direct built-in issue-search access and strengthen the scanner
eval to enforce the wrapper path and fail-closed behavior.
## Dependency
This PR depends on
[#133958](#133958),
which must merge first. The `/ci-eval` workflow restores evaluator
inputs from `main` before checking out the PR head; without the
prerequisite files already present on `main`, the wrapper, grader, and
focused tests are removed during restoration and this PR's evaluator
changes are not exercised.
## Motivation
The built-in issue search result projection can omit author metadata.
When that happens, existing bot-authored KBEs can be hidden from
duplicate detection even though prompt guidance requests the author
field. This makes the transport deterministic while leaving non-exact
query formulation and semantic comparison to the model.
This is narrower than #132619 for issue lookup: pull request searches
continue to use the GitHub MCP tools, while issue searches use the
dedicated wrapper.
## Scope limitation
The eval's direct-search protection is pattern-based and cannot
recognize every possible shell spelling or equivalent command. This is
an existing limitation of the eval setup and is out of scope for this
PR; the workflow's network policy and mandatory wrapper checks remain
the enforcement mechanisms for this change.
## Validation
- Compiled `ci-failure-scan` with gh-aw v0.86.2 and actionlint enabled.
- Passed Vally 0.14 strict lint for `ci-failure-scan.eval.yaml`.
- Passed actionlint v1.7.12 for `ci-eval.yml`.
- Exercised the wrapper against live KBE searches and confirmed
bot-authored candidates include their author logins.
- Passed the focused Node test suite with 10 tests.
> [!NOTE]
> This pull request description was generated by GitHub Copilot.
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
description: "Search only dotnet/runtime issues and return inert candidate identifiers with author metadata. Use issue_read to inspect every candidate."
56
+
inputs:
57
+
query:
58
+
type: string
59
+
required: true
60
+
description: "GitHub issue search query without a repo qualifier. The tool adds repo:dotnet/runtime and is:issue."
61
+
script: |
62
+
if (typeof query !== "string" || !query.trim()) {
63
+
throw new Error("query must be a non-empty string");
throw new Error("gh issue search returned invalid JSON");
89
+
}
90
+
if (result.incomplete_results !== false ||
91
+
!Array.isArray(result.items) ||
92
+
!Number.isInteger(result.total_count)) {
93
+
throw new Error("GitHub issue search returned an invalid response");
94
+
}
95
+
if (result.total_count > result.items.length) {
96
+
throw new Error("GitHub issue search returned more matches than the page limit; narrow the query");
97
+
}
98
+
if (result.total_count !== result.items.length) {
99
+
throw new Error("GitHub issue search returned an invalid response");
100
+
}
101
+
return result.items.map((item) => {
102
+
if (item.repository_url !== "https://api.github.com/repos/dotnet/runtime" ||
103
+
item.pull_request !== undefined ||
104
+
!Number.isInteger(item.number) ||
105
+
typeof item.user?.login !== "string" ||
106
+
item.user.login.length === 0) {
107
+
throw new Error("GitHub issue search returned an invalid candidate");
108
+
}
109
+
return {
110
+
number: item.number,
111
+
user: { login: item.user.login }
112
+
};
113
+
});
114
+
env:
115
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
116
+
49
117
checkout:
50
118
fetch-depth: 50
51
119
@@ -64,6 +132,9 @@ network:
64
132
- dev.azure.com
65
133
- helix.dot.net
66
134
- "*.blob.core.windows.net"
135
+
blocked:
136
+
- github
137
+
- api.github.com
67
138
---
68
139
69
140
# CI Outer-Loop Failure Scanner
@@ -90,6 +161,7 @@ The agent runs read-only. All writes go through `safe-outputs`.
90
161
10.**All intermediate state under `/tmp/gh-aw/agent/`.** Each bash invocation is a fresh subshell; persist anything you want to keep.
91
162
11.**AzDO API: anonymous only.** Stay on `_apis/build/...`. Never call `_apis/test/...` or `vstmr.dev.azure.com` (both redirect to sign-in).
92
163
12.**Don't add `area-*` references to issue titles.** Multi-area titles produce multi-label assignments from the labeler bot.
164
+
13.**Issue search transport is fixed.** Use only `search-kbe-issues` for GitHub issue searches, then inspect every returned candidate with `github``issue_read` method `get`. Never use `github``search_issues` or shell `gh` issue search.
93
165
94
166
## What this run must accomplish
95
167
@@ -292,6 +364,16 @@ Follow exactly these sections from `.github/workflows/shared/create-kbe.instruct
292
364
4.`<a id="search-existing-prs"></a>` / `## Search for existing PRs already handling the failure`
293
365
5.`<a id="verify-embedded-issues"></a>` / `## Verify every embedded issue number exists`
294
366
367
+
All issue-search query variants in those sections MUST go through
368
+
`search-kbe-issues`. The wrapper adds `repo:dotnet/runtime is:issue` and returns
369
+
at most 10 inert candidate records containing only the issue number and
370
+
`user.login`. For every returned number, call `github``issue_read` with
371
+
`owner: dotnet`, `repo: runtime`, and `method: get` before making any semantic
372
+
duplicate or tracker decision. A wrapper failure or integrity-filtered/failed
373
+
candidate read must fail closed as specified in the shared instructions.
374
+
Use the built-in `search_pull_requests` and `pull_request_read` tools only for
375
+
the PR searches in that flow.
376
+
295
377
When searching, account for the fact that the same signature can be filed in
296
378
different `ErrorMessage` representations. A KBE recorded in `<a id="kbe-array-form"></a>`
297
379
multi-line array form will not surface from a single-substring search, and vice
Copy file name to clipboardExpand all lines: .github/workflows/evals/ci-failure-scan.eval.yaml
+24-12Lines changed: 24 additions & 12 deletions
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,5 @@
1
1
name: ci-failure-scan-eval
2
-
description: Dynamic gate for the CI failure scanner's safe-output, run with the maintainer /ci-scan eval command. It runs the scanner against a live outer-loop failure it discovers itself, then checks the emitted Known Build Error against the static KBE format and that it actually grounded the KBE in a real failing build.
2
+
description: Dynamic gate for the CI failure scanner's safe-output, run with the maintainer /ci-scan eval command. It runs the scanner against a live outer-loop failure it discovers itself, then checks either the emitted Known Build Error or an explicit no-new-KBE result, and verifies that it actually grounded the decision in a real failing build.
1. Find a recent failed build on `main` of an outer-loop pipeline via the dnceng-public AzDO REST API. It is anonymous and needs no auth, so stay on `_apis/build/...`. Start with `runtime-extra-platforms`, which is definition 154, and fall back to other outer-loop defs if needed.
2. Open that build's timeline at `/_apis/build/builds/{id}/timeline?api-version=7.1`, pick a failed leg, and read its log at `/_apis/build/builds/{id}/logs/{logId}?api-version=7.1` to extract a concrete error or assertion signature. Save the leg log you extract from as `failure.log` under /tmp and grep it for your signature to get the hit count.
51
-
3. Before filing, check whether an equivalent KBE already exists so you would not duplicate it. Run `gh search issues 'in:title [ci-scan]' --repo dotnet/runtime --state open` and also search by your signature text. Assume you would file only if none matches.
51
+
3. Before filing, check whether an equivalent KBE already exists so you would not duplicate it. For this eval, this two-query step replaces the broader issue-search variants in the copied shared instructions; do not run additional issue-search variants. The eval runner does not launch workflow-frontmatter MCP servers, so invoke the trusted CLI harness exposed through `KBE_SEARCH_HELPER` with `node "$KBE_SEARCH_HELPER" '<query>'`. Use two bounded queries: one containing `in:title "[ci-scan]"` plus the discovered failure signature or test-family stem, and one containing `"[ci-scan]"` plus the most distinctive signature text. Choose terms specific enough that each query returns at most 10 complete matches; do not use an unbounded `[ci-scan]`-only query or retry after a helper error. For every candidate returned by either query, call the GitHub `issue_read` tool with `owner: dotnet`, `repo: runtime`, and `method: get` before deciding whether it matches. Do not use any other shell issue search or the built-in `search_issues` tool. Assume you would file only if every readable candidate is unrelated; fail closed if a candidate cannot be read.
52
52
53
-
Then **emit the create-issue safe-output you would file, as a single markdown document at `./out/kbe.md`**, in exactly this shape.
53
+
If every actionable failure is already represented by an existing KBE, or no actionable failure can be identified, write exactly `Result: No new Known Build Error` to `./out/kbe.md` and do not emit a create-issue request.
54
+
55
+
Otherwise, **emit the create-issue safe-output you would file, as a single markdown document at `./out/kbe.md`**, in exactly this shape.
54
56
55
57
- First line is `Title: [ci-scan] <one stable failure shape>`, an assertion string or test family with no build numbers, dates, queue or machine names, or run IDs.
56
58
- Second line is `Labels: Known Build Error, <blocking-clean-ci|blocking-clean-ci-optional>`. Pick exactly one blocking label. The JIT, GC, and PGO stress pipelines are `optional-ci` and use `blocking-clean-ci-optional`, and the required gate uses `blocking-clean-ci`.
@@ -61,38 +63,40 @@ stimuli:
61
63
62
64
rubric:
63
65
- |
64
-
The emitted `./out/kbe.md` conforms to the static Known Build Error format. It has a `[ci-scan] ` title naming exactly one failure shape with no run-specific IDs, the `Known Build Error` label plus exactly one of `blocking-clean-ci` or `blocking-clean-ci-optional`, the three body sections, a collapsed `KBE authoring guidance (ci-failure-scan)` block, and exactly one fenced json block with all four keys where exactly one of ErrorMessage or ErrorPattern is a non-empty specific substring rather than a bare identifier and the other is `""`. The Build Information section cites a concrete real build id. After the JSON, it has a collapsed `Agentic workflow metadata (ci-failure-scan)` block identifying the `kbe-verification` artifact and a positive verified match count.
66
+
The emitted `./out/kbe.md` is either exactly `Result: No new Known Build Error` when the live scan has no actionable new KBE, or it conforms to the static Known Build Error format. A KBE has a `[ci-scan] ` title naming exactly one failure shape with no run-specific IDs, the `Known Build Error` label plus exactly one of `blocking-clean-ci` or `blocking-clean-ci-optional`, the three body sections, a collapsed `KBE authoring guidance (ci-failure-scan)` block, and exactly one fenced json block with all four keys where exactly one of ErrorMessage or ErrorPattern is a non-empty specific substring rather than a bare identifier and the other is `""`. The Build Information section cites a concrete real build id. After the JSON, it has a collapsed `Agentic workflow metadata (ci-failure-scan)` block identifying the `kbe-verification` artifact and a positive verified match count.
67
+
- |
68
+
The trajectory invokes only the `search-kbe-issues` CLI harness for issue searches, parses every candidate returned by both required queries, and successfully calls `issue_read` for every returned issue number before deciding that no candidate matches. If any candidate cannot be read, the run fails closed instead of emitting `./out/kbe.md`.
65
69
66
70
graders:
67
71
- type: file-matches
68
72
config:
69
73
path: "out/kbe.md"
70
-
pattern: '(?im)^Title:\s*\[ci-scan\]\s+\S'
74
+
pattern: '(?ims)(?:(?<![\s\S])\s*Result:\s*No new Known Build Error\s*(?![\s\S])|Title:\s*\[ci-scan\]\s+\S)'
pattern: '(?ims)(?:(?<![\s\S])\s*Result:\s*No new Known Build Error\s*(?![\s\S])|```json\s*\{.*"ErrorMessage".*"ErrorPattern".*"BuildRetry".*"ExcludeConsoleLog".*\}\s*```)'
pattern: '(?ims)(?:(?<![\s\S])\s*Result:\s*No new Known Build Error\s*(?![\s\S])|<details>\s*<summary>KBE authoring guidance \(ci-failure-scan\)</summary>.*?</details>)'
91
95
92
96
- type: file-matches
93
97
config:
94
98
path: "out/kbe.md"
95
-
pattern: '(?s)<details>\s*<summary>Agentic workflow metadata \(ci-failure-scan\)</summary>\s*Workflow artifact:\s*ci-failure-scan\s*Artifact kind:\s*kbe-verification\s*Verified match count:\s*[1-9]\d*\s+hits in failure\.log\s*</details>'
99
+
pattern: '(?ims)(?:(?<![\s\S])\s*Result:\s*No new Known Build Error\s*(?![\s\S])|<details>\s*<summary>Agentic workflow metadata \(ci-failure-scan\)</summary>\s*Workflow artifact:\s*ci-failure-scan\s*Artifact kind:\s*kbe-verification\s*Verified match count:\s*[1-9]\d*\s+hits in failure\.log\s*</details>)'
0 commit comments