Skip to content

Commit b60b57b

Browse files
authored
feat: improve gemini issue triage workflow (#171)
This commit improves the gemini issue triage workflow by: - Passing the available labels as an environment variable to the Gemini CLI. - Using the output of the Gemini CLI to apply labels to the issue. - Adding a step to get all repository labels and pass them to the gemini-cli. - Updating the prompt to classify the issue and output the labels in JSON format. - Adding a step to apply the labels to the issue using the github-script. <img width="2258" height="1604" alt="image" src="https://github.com/user-attachments/assets/6e723bdd-cd6c-451f-930b-0310dee9d03a" />
1 parent 772c555 commit b60b57b

File tree

4 files changed

+354
-92
lines changed

4 files changed

+354
-92
lines changed

.github/workflows/gemini-issue-automated-triage.yml

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,31 @@ jobs:
5454
app-id: '${{ vars.APP_ID }}'
5555
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
5656

57-
- name: 'Run Gemini Issue Triage'
57+
- name: 'Get Repository Labels'
58+
id: 'get_labels'
59+
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
60+
with:
61+
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
62+
script: |-
63+
const { data: labels } = await github.rest.issues.listLabelsForRepo({
64+
owner: context.repo.owner,
65+
repo: context.repo.repo,
66+
});
67+
const labelNames = labels.map(label => label.name);
68+
core.setOutput('available_labels', labelNames.join(','));
69+
core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`);
70+
return labelNames;
71+
72+
- name: 'Run Gemini Issue Analysis'
5873
uses: './'
59-
id: 'gemini_issue_triage'
74+
id: 'gemini_issue_analysis'
6075
env:
61-
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
76+
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
6277
ISSUE_TITLE: '${{ github.event.issue.title }}'
6378
ISSUE_BODY: '${{ github.event.issue.body }}'
6479
ISSUE_NUMBER: '${{ github.event.issue.number }}'
6580
REPOSITORY: '${{ github.repository }}'
81+
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
6682
with:
6783
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
6884
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
@@ -77,9 +93,7 @@ jobs:
7793
"debug": ${{ fromJSON(env.DEBUG || env.ACTIONS_STEP_DEBUG || false) }},
7894
"maxSessionTurns": 25,
7995
"coreTools": [
80-
"run_shell_command(echo)",
81-
"run_shell_command(gh label list)",
82-
"run_shell_command(gh issue edit)"
96+
"run_shell_command(echo)"
8397
],
8498
"telemetry": {
8599
"enabled": true,
@@ -90,41 +104,88 @@ jobs:
90104
## Role
91105
92106
You are an issue triage assistant. Analyze the current GitHub issue
93-
and apply the most appropriate existing labels. Use the available
107+
and identify the most appropriate existing labels. Use the available
94108
tools to gather information; do not ask for information to be
95109
provided.
96110
97111
## Steps
98112
99-
1. Run: `gh label list` to get all available labels.
113+
1. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
100114
2. Review the issue title and body provided in the environment
101115
variables: "${ISSUE_TITLE}" and "${ISSUE_BODY}".
102-
3. Classify issues by their kind (bug, enhancement, documentation,
103-
cleanup, etc) and their priority (p0, p1, p2, p3). Set the
104-
labels accoridng to the format `kind/*` and `priority/*` patterns.
105-
4. Apply the selected labels to this issue using:
106-
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`
107-
5. If the "status/needs-triage" label is present, remove it using:
108-
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
116+
3. Classify the issue by the appropriate labels from the available labels.
117+
4. Output the appropriate labels for this issue in JSON format with explanation, for example:
118+
```
119+
{"labels_to_set": ["kind/bug", "priority/p0"], "explanation": "This is a critical bug report affecting main functionality"}
120+
```
121+
5. If the issue cannot be classified using the available labels, output:
122+
```
123+
{"labels_to_set": [], "explanation": "Unable to classify this issue with available labels"}
124+
```
109125
110126
## Guidelines
111127
112128
- Only use labels that already exist in the repository
113-
- Do not add comments or modify the issue content
114-
- Triage only the current issue
115129
- Assign all applicable labels based on the issue content
116130
- Reference all shell variables as "${VAR}" (with quotes and braces)
131+
- Output only valid JSON format
132+
- Do not include any explanation or additional text, just the JSON
133+
134+
- name: 'Apply Labels to Issue'
135+
if: |-
136+
${{ steps.gemini_issue_analysis.outputs.summary != '' }}
137+
env:
138+
REPOSITORY: '${{ github.repository }}'
139+
ISSUE_NUMBER: '${{ github.event.issue.number }}'
140+
LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}'
141+
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
142+
with:
143+
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
144+
script: |-
145+
// Strip code block markers if present
146+
const rawLabels = process.env.LABELS_OUTPUT;
147+
core.info(`Raw labels JSON: ${rawLabels}`);
148+
let parsedLabels;
149+
try {
150+
const trimmedLabels = rawLabels.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '').trim();
151+
parsedLabels = JSON.parse(trimmedLabels);
152+
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
153+
} catch (err) {
154+
core.setFailed(`Failed to parse labels JSON from Gemini output: ${err.message}\nRaw output: ${rawLabels}`);
155+
return;
156+
}
117157
118-
- name: 'Post Issue Triage Failure Comment'
158+
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
159+
160+
// Set labels based on triage result
161+
if (parsedLabels.labels_to_set && parsedLabels.labels_to_set.length > 0) {
162+
await github.rest.issues.setLabels({
163+
owner: context.repo.owner,
164+
repo: context.repo.repo,
165+
issue_number: issueNumber,
166+
labels: parsedLabels.labels_to_set
167+
});
168+
const explanation = parsedLabels.explanation ? ` - ${parsedLabels.explanation}` : '';
169+
core.info(`Successfully set labels for #${issueNumber}: ${parsedLabels.labels_to_set.join(', ')}${explanation}`);
170+
} else {
171+
// If no labels to set, leave the issue as is
172+
const explanation = parsedLabels.explanation ? ` - ${parsedLabels.explanation}` : '';
173+
core.info(`No labels to set for #${issueNumber}, leaving as is${explanation}`);
174+
}
175+
176+
- name: 'Post Issue Analysis Failure Comment'
119177
if: |-
120-
${{ failure() && steps.gemini_issue_triage.outcome == 'failure' }}
178+
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
179+
env:
180+
ISSUE_NUMBER: '${{ github.event.issue.number }}'
181+
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
121182
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
122183
with:
123184
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
124185
script: |-
125186
github.rest.issues.createComment({
126-
owner: '${{ github.repository }}'.split('/')[0],
127-
repo: '${{ github.repository }}'.split('/')[1],
128-
issue_number: '${{ github.event.issue.number }}',
129-
body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
187+
owner: context.repo.owner,
188+
repo: context.repo.repo,
189+
issue_number: parseInt(process.env.ISSUE_NUMBER),
190+
body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${process.env.RUN_URL}) for details.'
130191
})

.github/workflows/gemini-issue-scheduled-triage.yml

Lines changed: 93 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,31 @@ jobs:
6262
ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')"
6363
echo "✅ Found ${ISSUE_COUNT} issues to triage! 🎯"
6464
65-
- name: 'Run Gemini Issue Triage'
65+
- name: 'Get Repository Labels'
66+
id: 'get_labels'
67+
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
68+
with:
69+
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
70+
script: |-
71+
const { data: labels } = await github.rest.issues.listLabelsForRepo({
72+
owner: context.repo.owner,
73+
repo: context.repo.repo,
74+
});
75+
const labelNames = labels.map(label => label.name);
76+
core.setOutput('available_labels', labelNames.join(','));
77+
core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`);
78+
return labelNames;
79+
80+
- name: 'Run Gemini Issue Analysis'
6681
if: |-
6782
${{ steps.find_issues.outputs.issues_to_triage != '[]' }}
6883
uses: './'
69-
id: 'gemini_issue_triage'
84+
id: 'gemini_issue_analysis'
7085
env:
71-
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
86+
GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs
7287
ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}'
7388
REPOSITORY: '${{ github.repository }}'
89+
AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}'
7490
with:
7591
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
7692
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
@@ -85,10 +101,7 @@ jobs:
85101
"debug": ${{ fromJSON(env.DEBUG || env.ACTIONS_STEP_DEBUG || false) }},
86102
"maxSessionTurns": 25,
87103
"coreTools": [
88-
"run_shell_command(echo)",
89-
"run_shell_command(gh label list)",
90-
"run_shell_command(gh issue edit)",
91-
"run_shell_command(gh issue list)"
104+
"run_shell_command(echo)"
92105
],
93106
"telemetry": {
94107
"enabled": true,
@@ -98,26 +111,83 @@ jobs:
98111
prompt: |-
99112
## Role
100113
101-
You are an issue triage assistant. Analyze issues and apply
102-
appropriate labels. Use the available tools to gather information;
103-
do not ask for information to be provided.
114+
You are an issue triage assistant. Analyze the GitHub issues and
115+
identify the most appropriate existing labels to apply.
104116
105117
## Steps
106118
107-
1. Run: `gh label list`
108-
2. Check environment variable: "${ISSUES_TO_TRIAGE}" (JSON array
109-
of issues)
110-
3. For each issue, apply labels:
111-
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`.
112-
If available, set labels that follow the `kind/*`, `area/*`,
113-
and `priority/*` patterns.
114-
4. For each issue, if the `status/needs-triage` label is present,
115-
remove it using:
116-
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
119+
1. Review the available labels in the environment variable: "${AVAILABLE_LABELS}".
120+
2. Review the issues in the environment variable: "${ISSUES_TO_TRIAGE}".
121+
3. For each issue, classify it by the appropriate labels from the available labels.
122+
4. Output a JSON array of objects, each containing the issue number,
123+
the labels to set, and a brief explanation. For example:
124+
```
125+
[
126+
{
127+
"issue_number": 123,
128+
"labels_to_set": ["kind/bug", "priority/p2"],
129+
"explanation": "This is a bug report with high priority based on the error description"
130+
},
131+
{
132+
"issue_number": 456,
133+
"labels_to_set": ["kind/enhancement"],
134+
"explanation": "This is a feature request for improving the UI"
135+
}
136+
]
137+
```
138+
5. If an issue cannot be classified, do not include it in the output array.
117139
118140
## Guidelines
119141
120-
- Only use existing repository labels
121-
- Do not add comments
122-
- Triage each issue independently
142+
- Only use labels that already exist in the repository
143+
- Assign all applicable labels based on the issue content
123144
- Reference all shell variables as "${VAR}" (with quotes and braces)
145+
- Output only valid JSON format
146+
- Do not include any explanation or additional text, just the JSON
147+
148+
- name: 'Apply Labels to Issues'
149+
if: |-
150+
${{ steps.gemini_issue_analysis.outcome == 'success' &&
151+
steps.gemini_issue_analysis.outputs.summary != '[]' }}
152+
env:
153+
REPOSITORY: '${{ github.repository }}'
154+
LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}'
155+
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
156+
with:
157+
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
158+
script: |-
159+
// Strip code block markers if present
160+
const rawLabels = process.env.LABELS_OUTPUT;
161+
core.info(`Raw labels JSON: ${rawLabels}`);
162+
let parsedLabels;
163+
try {
164+
const trimmedLabels = rawLabels.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '').trim();
165+
parsedLabels = JSON.parse(trimmedLabels);
166+
core.info(`Parsed labels JSON: ${JSON.stringify(parsedLabels)}`);
167+
} catch (err) {
168+
core.setFailed(`Failed to parse labels JSON from Gemini output: ${err.message}\nRaw output: ${rawLabels}`);
169+
return;
170+
}
171+
172+
for (const entry of parsedLabels) {
173+
const issueNumber = entry.issue_number;
174+
if (!issueNumber) {
175+
core.info(`Skipping entry with no issue number: ${JSON.stringify(entry)}`);
176+
continue;
177+
}
178+
179+
// Set labels based on triage result
180+
if (entry.labels_to_set && entry.labels_to_set.length > 0) {
181+
await github.rest.issues.setLabels({
182+
owner: context.repo.owner,
183+
repo: context.repo.repo,
184+
issue_number: issueNumber,
185+
labels: entry.labels_to_set
186+
});
187+
const explanation = entry.explanation ? ` - ${entry.explanation}` : '';
188+
core.info(`Successfully set labels for #${issueNumber}: ${entry.labels_to_set.join(', ')}${explanation}`);
189+
} else {
190+
// If no labels to set, leave the issue as is
191+
core.info(`No labels to set for #${issueNumber}, leaving as is`);
192+
}
193+
}

0 commit comments

Comments
 (0)