From 8874261549734f9fe5462651fc0cdac8a06b1987 Mon Sep 17 00:00:00 2001 From: PureWeen <223556219+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:46:13 -0500 Subject: [PATCH 1/2] Update issue triage safeguards Rebase the issue triage type policy onto current main, preserve existing issue types, and enforce per-output staged mutations with gh-aw v0.87.10. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bcb017f1-64bc-4c44-81c0-96d2b6cc3bce --- .github/workflows/issue-triage-agent.lock.yml | 40 ++++++++++- .github/workflows/issue-triage-agent.md | 68 ++++++++++++++++--- 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index 68f074279c1d..de0c74578803 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5fdfd6a25aeb5104b54e6e86f1e56cbe50c3eeea0293cb7b11a1192840ddbb6b","body_hash":"19919bfd6cc21def311f85ac5d709372b1ccbebc0aad764a4adb86ed6f55136e","compiler_version":"v0.87.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d735f6c711587b5b80361988bb4a2297c318ea456ecbbefe8c5d4226725407f9","body_hash":"89cfd2759aafae8a2bd37b94ef8a7c1d2dbd85cc2df8be1bf2a37fcbc928f6b6","compiler_version":"v0.87.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc8c008a419c5b7a29df6f5641edd35fd1c6ea85","version":"v0.87.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.10","digest":"sha256:c01e6d16d11ea4f2a46cc023a9f402224a3b3861b026818eec0dc586d7e6918e","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.10@sha256:c01e6d16d11ea4f2a46cc023a9f402224a3b3861b026818eec0dc586d7e6918e"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10","digest":"sha256:c3a18aebb8251339117ea998296315de17bada366f8d03919b3348ea71112e64","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10@sha256:c3a18aebb8251339117ea998296315de17bada366f8d03919b3348ea71112e64"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.10","digest":"sha256:c06076f7aca95df713e0748c44d80c0a3c2538fad67bfdd04296d45158e083e6","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.10@sha256:c06076f7aca95df713e0748c44d80c0a3c2538fad67bfdd04296d45158e083e6"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.14","digest":"sha256:b2f0c2b2f17b5fbe809e5bb99dc185b6ddd70df25295dc63a6d526350334eff5","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.14@sha256:b2f0c2b2f17b5fbe809e5bb99dc185b6ddd70df25295dc63a6d526350334eff5"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop","remove_labels","set_issue_type"]}]} # This file was automatically generated by gh-aw (v0.87.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -106,6 +106,7 @@ env: jobs: activation: needs: + - issue_context - pat_pool - pre_activation if: > @@ -317,6 +318,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} GH_AW_STEPS_SANITIZED_OUTPUTS_TITLE: ${{ steps.sanitized.outputs.title }} GH_AW_PROMPT_CONTENT_0000: "\n" @@ -340,6 +342,7 @@ jobs: GH_AW_GITHUB_EVENT_INPUTS_ISSUE_NUMBER: ${{ github.event.inputs.issue_number }} GH_AW_EXPR_726AD1D2: ${{ github.event.issue.number || github.event.inputs.issue_number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} GH_AW_STEPS_SANITIZED_OUTPUTS_TITLE: ${{ steps.sanitized.outputs.title }} with: @@ -367,6 +370,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: ${{ needs.pat_pool.outputs.pat_number }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} @@ -397,6 +401,7 @@ jobs: GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: process.env.GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE, GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: process.env.GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: process.env.GH_AW_STEPS_SANITIZED_OUTPUTS_BODY, @@ -439,6 +444,7 @@ jobs: agent: needs: - activation + - issue_context - pat_pool if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest @@ -607,7 +613,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\",\"Epic\"],\"max\":1}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\"],\"max\":1,\"staged\":\"\"}}" with: script: | const path = require('path'); @@ -1281,6 +1287,7 @@ jobs: - activation - agent - detection + - issue_context - pat_pool - safe_outputs if: > @@ -1820,6 +1827,32 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json + issue_context: + name: Read trusted issue metadata + runs-on: ubuntu-latest + permissions: + issues: read + outputs: + issue_type: ${{ steps.issue.outputs.issue_type }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Read current issue type + id: issue + run: | + issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')" + echo "issue_type=${issue_type}" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue_number }} + pat_pool: needs: pre_activation runs-on: ubuntu-slim @@ -1941,6 +1974,7 @@ jobs: - activation - agent - detection + - issue_context if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim environment: copilot-pat-pool @@ -2032,7 +2066,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\",\"Epic\"],\"max\":1}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\"],\"max\":1,\"staged\":\"${{ needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }}\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-triage-agent.md b/.github/workflows/issue-triage-agent.md index e3a932d225a2..11d4da5ce5a6 100644 --- a/.github/workflows/issue-triage-agent.md +++ b/.github/workflows/issue-triage-agent.md @@ -39,6 +39,24 @@ concurrency: job-discriminator: ${{ github.event.issue.number || github.event.inputs.issue_number || github.run_id }} queue: max +jobs: + issue_context: + name: Read trusted issue metadata + runs-on: ubuntu-latest + permissions: + issues: read + outputs: + issue_type: ${{ steps.issue.outputs.issue_type }} + steps: + - name: Read current issue type + id: issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue_number }} + run: | + issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')" + echo "issue_type=${issue_type}" >> "$GITHUB_OUTPUT" + tools: bash: ["cat", "head", "tail", "grep", "wc", "jq"] github: @@ -46,11 +64,13 @@ tools: safe-outputs: report-failure-as-issue: false + needs: [issue_context] noop: report-as-issue: false set-issue-type: - allowed: ["Bug", "Feature", "Task", "Epic"] + allowed: ["Bug", "Feature", "Task"] max: 1 + staged: ${{ needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }} add-labels: allowed: - area-auth @@ -80,9 +100,11 @@ safe-outputs: - test-failure - performance max: 3 + staged: ${{ github.event.inputs.dry_run == 'true' }} remove-labels: allowed: [needs-area-label] max: 1 + staged: ${{ github.event.inputs.dry_run == 'true' }} add-comment: max: 1 target: "*" @@ -114,7 +136,7 @@ You are an issue-triage agent for the **dotnet/aspnetcore** repository. Your job is to analyze a newly opened issue and perform four tasks: 1. **Area classification** - assign the correct `area-*` label -2. **Type classification** - assign an issue type (not a label) (Bug, Feature, Task, or Epic) +2. **Type classification** - preserve an existing issue type, or assign Bug, Feature, or Task 3. **Duplicate detection** - search for similar existing issues 4. **Triage comment** - post a single summary comment on the issue (unless the vulnerability gate below suppresses it) @@ -127,13 +149,15 @@ You **must** obtain the real issue title and body before doing anything else. Tw sources are available — use whichever is populated: - **Number:** #${{ github.event.issue.number || github.event.inputs.issue_number }} +- **Current issue type (trusted metadata):** ${{ needs.issue_context.outputs.issue_type }} - **Title (from payload):** ${{ steps.sanitized.outputs.title }} - **Body (from payload):** ${{ steps.sanitized.outputs.body }} **If both the title and body above are populated**, use them directly as the source -of truth and **skip the MCP fetch entirely.** +of truth, treat the current issue type above as trusted workflow metadata, and +**skip the MCP fetch entirely.** A non-empty current issue type is authoritative. **If the title or body above is empty, that is normal — not an error.** The payload is intentionally blank in two common cases: (a) `workflow_dispatch` runs, which do @@ -145,6 +169,8 @@ with the **github** MCP server's `issue_read` tool before proceeding: - Call `issue_read` with owner `dotnet`, repo `aspnetcore`, and issue number `${{ github.event.issue.number || github.event.inputs.issue_number }}`. +- Capture the issue's current type returned by `issue_read` along with its title, + body, and labels. Treat any non-empty current type as authoritative. - This `issue_read` call is **required, not optional.** An empty payload is never a reason to stop: do **not** report missing data, do **not** call `noop`, and do **not** give up before you have successfully called `issue_read`. @@ -437,12 +463,27 @@ Explain why in the comment instead. ## Step 2: Type Classification -Classify the issue into one of these types: +First inspect the trusted current issue type collected above. + +- If it is non-empty, preserve it exactly and do not recommend or apply a + replacement type. This includes maintainer-created `Epic` issues and + template- or automation-assigned `Bug`, `Feature`, or `Task` issues. +- If it is empty, classify the issue into exactly one of these types: | Type | When to use | |-----------|-------------| -| `Bug` | The report clearly identifies a behavior as a bug and it can be reproduced. Something is broken or behaving unexpectedly compared to its intended design. | +| `Bug` | The report clearly identifies a behavior as a bug and it can be reproduced. Something is broken or behaving unexpectedly compared to its intended design. A small or mechanical fix to broken shipped behavior is still a Bug — the deciding factor is that current behavior is broken, not the size of the fix. | | `Feature` | The report asks for a behavior that is not currently implemented. This may be a brand-new feature or an addition/enhancement to an existing feature. | +| `Task` | Bounded maintenance, documentation, test, infrastructure, or refactoring work where current behavior is not broken. A docs-only deliverable gets the existing `docs` sub-type label alongside this type. | + +`Epic` remains valid maintainer-managed planning metadata in the dotnet +organization, but this automated intake workflow must never assign it. A broad +or large single feature request remains a `Feature`; implementation size alone +does not change its type. + +Preserving an existing type changes only the type mutation decision. Continue +the complete area, subtype, duplicate, vulnerability-gate, comment, and no-op +analysis normally. ## Step 3: Additional Labels @@ -512,7 +553,7 @@ structure — no additional sections beyond what is listed below: ### Triage Summary **Area:** `area-xyz` (brief reason) -**Type:** `Bug` | `Feature` (brief reason) +**Type:** `` (preserved) | `Bug` | `Feature` | `Task` (brief reason) #### Regression Info - **Previously working version:** .NET x.y / ASP.NET Core x.y @@ -650,7 +691,9 @@ no Notes section. Order of operations matters. Do these in this exact order: -1. **Decide the labels and issue type** you will apply, based on Steps 1–5. +1. **Decide the labels and type action** based on Steps 1–5. A preserved + existing type is not a reason to skip area, sub-type, duplicate, comment, + vulnerability-gate, removal, or no-op analysis. 2. **Apply the area label** and (if applicable from Step 3) one **additional sub-type label** using the `add-labels` safe output. The `add-labels` @@ -661,10 +704,13 @@ Order of operations matters. Do these in this exact order: step 3 below. Pass `item_number` explicitly, using `${{ github.event.issue.number || github.event.inputs.issue_number }}`. -3. **Apply the issue type** using `set-issue-type` with one of `Bug`, - `Feature`, `Task`, or `Epic` based on your Step 2 classification. Call - `set-issue-type` exactly once and pass `issue_number` explicitly, using - `${{ github.event.issue.number || github.event.inputs.issue_number }}`. +3. **Handle the issue type** based on the trusted current value: + - If the current issue type is non-empty, report it as preserved and do + **not** call `set-issue-type`. + - If the current issue type is empty, apply exactly one of `Bug`, `Feature`, + or `Task` using `set-issue-type`. Call `set-issue-type` exactly once and + pass `issue_number` explicitly, using + `${{ github.event.issue.number || github.event.inputs.issue_number }}`. 4. If the issue currently has `needs-area-label` and you assigned an area, **remove `needs-area-label`** using `remove-labels`. Pass `item_number` From 722a95da045b91cd0691642db7f790775ddc4230 Mon Sep 17 00:00:00 2001 From: PureWeen <223556219+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:22:53 -0500 Subject: [PATCH 2/2] Add fail-closed metadata lookup for issue type The issue_context job's type lookup conflates 'lookup failed' with 'issue is untyped', so a transient API error (502, network timeout) cascades through the job graph and kills the entire triage run. Add an explicit lookup_succeeded output that separates the trust channel from the value channel: - Failed/impossible lookup: lookup_succeeded=false, issue_type='' Type mutation fails closed (staged); area/labels/comment continue. - Successful untyped lookup: lookup_succeeded=true, issue_type='' Type assignment proceeds normally. - Successful typed lookup: lookup_succeeded=true, issue_type= Existing type preserved (staged). The step never fails: the gh api call is inside an if-compound that suppresses errexit. Input is sanitized (single-line enforcement). The set-issue-type staged expression gains a leading 'lookup_succeeded != true' clause so the handler blocks type writes whenever the lookup is untrusted. The prompt gains a three-way rule matching the same states and instructs the agent not to call set-issue-type when the lookup failed. Compiled with gh aw v0.87.10 strict, 0 warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7098ed82-dcc7-447e-8dd0-09e68f10ce22 --- .github/workflows/issue-triage-agent.lock.yml | 23 +++++- .github/workflows/issue-triage-agent.md | 79 +++++++++++++++---- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index de0c74578803..bd333dada3da 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d735f6c711587b5b80361988bb4a2297c318ea456ecbbefe8c5d4226725407f9","body_hash":"89cfd2759aafae8a2bd37b94ef8a7c1d2dbd85cc2df8be1bf2a37fcbc928f6b6","compiler_version":"v0.87.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6692a8a9e92f28552219d65e6c332294ff60bec705904b949d6d075e15e28871","body_hash":"efcd4a2f6148fb143c80349dcd1ce2a6fcbffc8d4181f3866049569aa7c1ddad","compiler_version":"v0.87.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc8c008a419c5b7a29df6f5641edd35fd1c6ea85","version":"v0.87.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.10","digest":"sha256:c01e6d16d11ea4f2a46cc023a9f402224a3b3861b026818eec0dc586d7e6918e","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.10@sha256:c01e6d16d11ea4f2a46cc023a9f402224a3b3861b026818eec0dc586d7e6918e"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10","digest":"sha256:c3a18aebb8251339117ea998296315de17bada366f8d03919b3348ea71112e64","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.10@sha256:c3a18aebb8251339117ea998296315de17bada366f8d03919b3348ea71112e64"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.10","digest":"sha256:c06076f7aca95df713e0748c44d80c0a3c2538fad67bfdd04296d45158e083e6","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.10@sha256:c06076f7aca95df713e0748c44d80c0a3c2538fad67bfdd04296d45158e083e6"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.14","digest":"sha256:b2f0c2b2f17b5fbe809e5bb99dc185b6ddd70df25295dc63a6d526350334eff5","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.14@sha256:b2f0c2b2f17b5fbe809e5bb99dc185b6ddd70df25295dc63a6d526350334eff5"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","missing_data","missing_tool","noop","remove_labels","set_issue_type"]}]} # This file was automatically generated by gh-aw (v0.87.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -319,6 +319,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_LOOKUP_SUCCEEDED: ${{ needs.issue_context.outputs.lookup_succeeded }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} GH_AW_STEPS_SANITIZED_OUTPUTS_TITLE: ${{ steps.sanitized.outputs.title }} GH_AW_PROMPT_CONTENT_0000: "\n" @@ -343,6 +344,7 @@ jobs: GH_AW_EXPR_726AD1D2: ${{ github.event.issue.number || github.event.inputs.issue_number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_LOOKUP_SUCCEEDED: ${{ needs.issue_context.outputs.lookup_succeeded }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} GH_AW_STEPS_SANITIZED_OUTPUTS_TITLE: ${{ steps.sanitized.outputs.title }} with: @@ -371,6 +373,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: ${{ needs.issue_context.outputs.issue_type }} + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_LOOKUP_SUCCEEDED: ${{ needs.issue_context.outputs.lookup_succeeded }} GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: ${{ needs.pat_pool.outputs.pat_number }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: ${{ steps.sanitized.outputs.body }} @@ -402,6 +405,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE: process.env.GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_ISSUE_TYPE, + GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_LOOKUP_SUCCEEDED: process.env.GH_AW_NEEDS_ISSUE_CONTEXT_OUTPUTS_LOOKUP_SUCCEEDED, GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: process.env.GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, GH_AW_STEPS_SANITIZED_OUTPUTS_BODY: process.env.GH_AW_STEPS_SANITIZED_OUTPUTS_BODY, @@ -1834,6 +1838,7 @@ jobs: issues: read outputs: issue_type: ${{ steps.issue.outputs.issue_type }} + lookup_succeeded: ${{ steps.issue.outputs.lookup_succeeded }} steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -1847,8 +1852,20 @@ jobs: - name: Read current issue type id: issue run: | - issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')" + # A failed or impossible lookup must never be reported as "this issue has no + # type". It is reported as lookup_succeeded=false with an empty issue_type so + # that type mutation fails closed while the rest of triage stays available. + issue_type="" + lookup_succeeded="false" + if [ -n "${ISSUE_NUMBER}" ] && issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')"; then + lookup_succeeded="true" + else + issue_type="" + fi + # Keep the value single-line so it cannot forge additional step outputs. + issue_type="$(printf '%s' "${issue_type}" | tr -d '\r\n')" echo "issue_type=${issue_type}" >> "$GITHUB_OUTPUT" + echo "lookup_succeeded=${lookup_succeeded}" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue_number }} @@ -2066,7 +2083,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\"],\"max\":1,\"staged\":\"${{ needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }}\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"area-auth\",\"area-blazor\",\"area-commandlinetools\",\"area-dataprotection\",\"area-grpc\",\"area-healthchecks\",\"area-hosting\",\"area-identity\",\"area-infrastructure\",\"area-middleware\",\"area-minimal\",\"area-mvc\",\"area-networking\",\"area-perf\",\"area-routing\",\"area-security\",\"area-signalr\",\"area-ui-rendering\",\"area-unified-build\",\"by-design\",\"question\",\"external\",\"docs\",\"api-proposal\",\"test-failure\",\"performance\"],\"max\":3,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"needs-area-label\"],\"max\":1,\"staged\":\"${{ github.event.inputs.dry_run == 'true' }}\"},\"report_incomplete\":{},\"set_issue_type\":{\"allowed\":[\"Bug\",\"Feature\",\"Task\"],\"max\":1,\"staged\":\"${{ needs.issue_context.outputs.lookup_succeeded != 'true' || needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }}\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-triage-agent.md b/.github/workflows/issue-triage-agent.md index 11d4da5ce5a6..fcf7cc735ee0 100644 --- a/.github/workflows/issue-triage-agent.md +++ b/.github/workflows/issue-triage-agent.md @@ -47,6 +47,7 @@ jobs: issues: read outputs: issue_type: ${{ steps.issue.outputs.issue_type }} + lookup_succeeded: ${{ steps.issue.outputs.lookup_succeeded }} steps: - name: Read current issue type id: issue @@ -54,8 +55,20 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue_number }} run: | - issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')" + # A failed or impossible lookup must never be reported as "this issue has no + # type". It is reported as lookup_succeeded=false with an empty issue_type so + # that type mutation fails closed while the rest of triage stays available. + issue_type="" + lookup_succeeded="false" + if [ -n "${ISSUE_NUMBER}" ] && issue_type="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE_NUMBER}" --jq '.type.name // ""')"; then + lookup_succeeded="true" + else + issue_type="" + fi + # Keep the value single-line so it cannot forge additional step outputs. + issue_type="$(printf '%s' "${issue_type}" | tr -d '\r\n')" echo "issue_type=${issue_type}" >> "$GITHUB_OUTPUT" + echo "lookup_succeeded=${lookup_succeeded}" >> "$GITHUB_OUTPUT" tools: bash: ["cat", "head", "tail", "grep", "wc", "jq"] @@ -70,7 +83,7 @@ safe-outputs: set-issue-type: allowed: ["Bug", "Feature", "Task"] max: 1 - staged: ${{ needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }} + staged: ${{ needs.issue_context.outputs.lookup_succeeded != 'true' || needs.issue_context.outputs.issue_type != '' || github.event.inputs.dry_run == 'true' }} add-labels: allowed: - area-auth @@ -150,14 +163,29 @@ sources are available — use whichever is populated: - **Number:** #${{ github.event.issue.number || github.event.inputs.issue_number }} - **Current issue type (trusted metadata):** ${{ needs.issue_context.outputs.issue_type }} +- **Current issue type lookup status:** ${{ needs.issue_context.outputs.lookup_succeeded }} - **Title (from payload):** ${{ steps.sanitized.outputs.title }} - **Body (from payload):** ${{ steps.sanitized.outputs.body }} +**Read the lookup status before you read the current issue type.** The current +issue type field above is only meaningful when the lookup status is exactly +`true`. + +- Lookup status `true` and a non-empty current type: the issue is typed, and that + type is authoritative. +- Lookup status `true` and an empty current type: the issue is genuinely untyped. +- Lookup status anything other than `true` (including `false` or blank): the + trusted lookup **failed**. The current type is **unknown**, not empty. Never + treat this as proof that the issue is untyped, and never use it to justify + assigning a type. Fall back to the `issue_read` MCP tool described below to + learn the real current type, and follow the unknown-type rules in Step 2. + **If both the title and body above are populated**, use them directly as the source -of truth, treat the current issue type above as trusted workflow metadata, and -**skip the MCP fetch entirely.** A non-empty current issue type is authoritative. +of truth, treat the current issue type above as trusted workflow metadata when the +lookup status is `true`, and **skip the MCP fetch entirely** unless the lookup +status is not `true`. A non-empty current issue type is authoritative. **If the title or body above is empty, that is normal — not an error.** The payload is intentionally blank in two common cases: (a) `workflow_dispatch` runs, which do @@ -170,7 +198,9 @@ with the **github** MCP server's `issue_read` tool before proceeding: - Call `issue_read` with owner `dotnet`, repo `aspnetcore`, and issue number `${{ github.event.issue.number || github.event.inputs.issue_number }}`. - Capture the issue's current type returned by `issue_read` along with its title, - body, and labels. Treat any non-empty current type as authoritative. + body, and labels. Treat any non-empty current type as authoritative. This is + also the fallback source of truth whenever the trusted lookup status above is + not `true`. - This `issue_read` call is **required, not optional.** An empty payload is never a reason to stop: do **not** report missing data, do **not** call `noop`, and do **not** give up before you have successfully called `issue_read`. @@ -463,12 +493,22 @@ Explain why in the comment instead. ## Step 2: Type Classification -First inspect the trusted current issue type collected above. - -- If it is non-empty, preserve it exactly and do not recommend or apply a - replacement type. This includes maintainer-created `Epic` issues and - template- or automation-assigned `Bug`, `Feature`, or `Task` issues. -- If it is empty, classify the issue into exactly one of these types: +First inspect the trusted current issue type lookup status collected above, then +the trusted current issue type itself. + +- If the lookup status is not exactly `true`, the current type is **unknown**. + Use the type reported by `issue_read` if you obtained one: if that is + non-empty, preserve it. If you have no reliable current type at all, do not + assert that the issue is untyped, do not claim a type was applied, and report + the type as `unknown (type lookup unavailable)` in your comment. The workflow + independently blocks type mutation in this state, so any `set-issue-type` + call you make will only be staged, never applied. +- If the lookup status is `true` and the current type is non-empty, preserve it + exactly and do not recommend or apply a replacement type. This includes + maintainer-created `Epic` issues and template- or automation-assigned `Bug`, + `Feature`, or `Task` issues. +- If the lookup status is `true` and the current type is empty, the issue is + genuinely untyped: classify it into exactly one of these types: | Type | When to use | |-----------|-------------| @@ -553,7 +593,7 @@ structure — no additional sections beyond what is listed below: ### Triage Summary **Area:** `area-xyz` (brief reason) -**Type:** `` (preserved) | `Bug` | `Feature` | `Task` (brief reason) +**Type:** `` (preserved) | `unknown (type lookup unavailable)` | `Bug` | `Feature` | `Task` (brief reason) #### Regression Info - **Previously working version:** .NET x.y / ASP.NET Core x.y @@ -705,12 +745,21 @@ Order of operations matters. Do these in this exact order: `${{ github.event.issue.number || github.event.inputs.issue_number }}`. 3. **Handle the issue type** based on the trusted current value: + - If the current issue type lookup status is not exactly `true`, the current + type is unknown. Do **not** call `set-issue-type` at all. Type mutation is + blocked by the workflow in this state regardless of what you emit, so a + call would be staged and silently discarded. Report the type as preserved + if `issue_read` gave you one, otherwise as + `unknown (type lookup unavailable)`. - If the current issue type is non-empty, report it as preserved and do **not** call `set-issue-type`. - - If the current issue type is empty, apply exactly one of `Bug`, `Feature`, - or `Task` using `set-issue-type`. Call `set-issue-type` exactly once and - pass `issue_number` explicitly, using + - If the current issue type is empty and the lookup was reliable, apply + exactly one of `Bug`, `Feature`, or `Task` using `set-issue-type`. Call + `set-issue-type` exactly once and pass `issue_number` explicitly, using `${{ github.event.issue.number || github.event.inputs.issue_number }}`. + - Area labels, sub-type labels, `needs-area-label` removal, and the triage + comment are never blocked by an unavailable type lookup. Continue with them + normally. 4. If the issue currently has `needs-area-label` and you assigned an area, **remove `needs-area-label`** using `remove-labels`. Pass `item_number`