From 029f6ae2a646d0b620e4e82a01d64935991738b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:01:13 +0000 Subject: [PATCH 1/4] Initial plan From da23be96229392d6cdc0179912752451bb9ceff9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:05:06 +0000 Subject: [PATCH 2/4] Filter AI suggested labels against available labels to prevent hallucinations Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/reusable-issue-triage.yml | 63 ++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/.github/workflows/reusable-issue-triage.yml b/.github/workflows/reusable-issue-triage.yml index 033eaa6..fa648de 100644 --- a/.github/workflows/reusable-issue-triage.yml +++ b/.github/workflows/reusable-issue-triage.yml @@ -108,25 +108,49 @@ jobs: uses: actions/github-script@v8 env: AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} + AVAILABLE_LABELS: ${{ env.AVAILABLE_LABELS }} with: script: | const response = process.env.AI_RESPONSE; + const availableLabelsStr = process.env.AVAILABLE_LABELS; + if (!response || response.trim() === '') { console.log('No labels selected by AI'); return; } - const labels = response.replaceAll('```', '').split(',') + // Parse available labels + const availableLabels = availableLabelsStr.split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); + + // Parse AI suggested labels + const suggestedLabels = response.replaceAll('```', '').split(',') .map(l => l.trim()) .filter(l => l.length > 0); - if (labels.length > 0) { - console.log(`Applying labels: ${labels.join(', ')}`); + // Filter to only use labels that exist in the repository + const validLabels = suggestedLabels.filter(label => + availableLabels.includes(label) + ); + + // Log any invalid labels + const invalidLabels = suggestedLabels.filter(label => + !availableLabels.includes(label) + ); + if (invalidLabels.length > 0) { + console.log( + `Skipping invalid labels: ${invalidLabels.join(', ')}` + ); + } + + if (validLabels.length > 0) { + console.log(`Applying labels: ${validLabels.join(', ')}`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - labels: labels + labels: validLabels }); } else { console.log('No valid labels to apply'); @@ -317,27 +341,50 @@ jobs: env: AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} ITEM_NUMBER: ${{ inputs.issue_number }} + AVAILABLE_LABELS: ${{ env.AVAILABLE_LABELS }} with: script: | const response = process.env.AI_RESPONSE; const itemNumber = parseInt(process.env.ITEM_NUMBER); + const availableLabelsStr = process.env.AVAILABLE_LABELS; if (!response || response.trim() === '') { console.log('No labels selected by AI'); return; } - const labels = response.replaceAll('```', '').split(',') + // Parse available labels + const availableLabels = availableLabelsStr.split(',') .map(l => l.trim()) .filter(l => l.length > 0); - if (labels.length > 0) { - console.log(`Applying labels: ${labels.join(', ')}`); + // Parse AI suggested labels + const suggestedLabels = response.replaceAll('```', '').split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); + + // Filter to only use labels that exist in the repository + const validLabels = suggestedLabels.filter(label => + availableLabels.includes(label) + ); + + // Log any invalid labels + const invalidLabels = suggestedLabels.filter(label => + !availableLabels.includes(label) + ); + if (invalidLabels.length > 0) { + console.log( + `Skipping invalid labels: ${invalidLabels.join(', ')}` + ); + } + + if (validLabels.length > 0) { + console.log(`Applying labels: ${validLabels.join(', ')}`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: itemNumber, - labels: labels + labels: validLabels }); } else { console.log('No valid labels to apply'); From 281ef24d6c680a86209eff20b1890c403b1ae07f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:06:08 +0000 Subject: [PATCH 3/4] Add null check for AVAILABLE_LABELS to prevent errors Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/reusable-issue-triage.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/reusable-issue-triage.yml b/.github/workflows/reusable-issue-triage.yml index fa648de..10b02a2 100644 --- a/.github/workflows/reusable-issue-triage.yml +++ b/.github/workflows/reusable-issue-triage.yml @@ -119,6 +119,11 @@ jobs: return; } + if (!availableLabelsStr) { + console.log('No available labels found'); + return; + } + // Parse available labels const availableLabels = availableLabelsStr.split(',') .map(l => l.trim()) @@ -353,6 +358,11 @@ jobs: return; } + if (!availableLabelsStr) { + console.log('No available labels found'); + return; + } + // Parse available labels const availableLabels = availableLabelsStr.split(',') .map(l => l.trim()) From b4ffb47a5ba4ed33f1f9c13f73b3395e0caee22e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:28:47 +0000 Subject: [PATCH 4/4] Use case-insensitive comparison and single-pass reduce for label validation Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/reusable-issue-triage.yml | 42 +++++++++++++-------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/.github/workflows/reusable-issue-triage.yml b/.github/workflows/reusable-issue-triage.yml index 10b02a2..1a78f33 100644 --- a/.github/workflows/reusable-issue-triage.yml +++ b/.github/workflows/reusable-issue-triage.yml @@ -124,9 +124,9 @@ jobs: return; } - // Parse available labels + // Parse available labels (normalized to lowercase) const availableLabels = availableLabelsStr.split(',') - .map(l => l.trim()) + .map(l => l.trim().toLowerCase()) .filter(l => l.length > 0); // Parse AI suggested labels @@ -134,15 +134,20 @@ jobs: .map(l => l.trim()) .filter(l => l.length > 0); - // Filter to only use labels that exist in the repository - const validLabels = suggestedLabels.filter(label => - availableLabels.includes(label) + // Partition into valid and invalid in a single pass + const { validLabels, invalidLabels } = suggestedLabels.reduce( + (acc, label) => { + if (availableLabels.includes(label.toLowerCase())) { + acc.validLabels.push(label); + } else { + acc.invalidLabels.push(label); + } + return acc; + }, + { validLabels: [], invalidLabels: [] } ); // Log any invalid labels - const invalidLabels = suggestedLabels.filter(label => - !availableLabels.includes(label) - ); if (invalidLabels.length > 0) { console.log( `Skipping invalid labels: ${invalidLabels.join(', ')}` @@ -363,9 +368,9 @@ jobs: return; } - // Parse available labels + // Parse available labels (normalized to lowercase) const availableLabels = availableLabelsStr.split(',') - .map(l => l.trim()) + .map(l => l.trim().toLowerCase()) .filter(l => l.length > 0); // Parse AI suggested labels @@ -373,15 +378,20 @@ jobs: .map(l => l.trim()) .filter(l => l.length > 0); - // Filter to only use labels that exist in the repository - const validLabels = suggestedLabels.filter(label => - availableLabels.includes(label) + // Partition into valid and invalid in a single pass + const { validLabels, invalidLabels } = suggestedLabels.reduce( + (acc, label) => { + if (availableLabels.includes(label.toLowerCase())) { + acc.validLabels.push(label); + } else { + acc.invalidLabels.push(label); + } + return acc; + }, + { validLabels: [], invalidLabels: [] } ); // Log any invalid labels - const invalidLabels = suggestedLabels.filter(label => - !availableLabels.includes(label) - ); if (invalidLabels.length > 0) { console.log( `Skipping invalid labels: ${invalidLabels.join(', ')}`