Skip to content

Commit 3916c88

Browse files
committed
feat: improve gemini issue triage workflow
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.
1 parent 772c555 commit 3916c88

File tree

4 files changed

+414
-92
lines changed

4 files changed

+414
-92
lines changed

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

Lines changed: 99 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,103 @@ 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, for example:
118+
```
119+
{"labels_to_add": ["kind/bug", "priority/p2"], "labels_to_remove": ["status/needs-triage"]}
120+
```
121+
5. If the issue cannot be classified using the available labels, output:
122+
```
123+
{"labels_to_add": [], "labels_to_remove": []}
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
117133
118-
- name: 'Post Issue Triage Failure Comment'
134+
- name: 'Apply Labels to Issue'
119135
if: |-
120-
${{ failure() && steps.gemini_issue_triage.outcome == 'failure' }}
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+
}
157+
158+
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
159+
160+
// Apply labels
161+
if (parsedLabels.labels_to_add && parsedLabels.labels_to_add.length > 0) {
162+
core.info(`Adding labels to #${issueNumber}: ${parsedLabels.labels_to_add.join(', ')}`)
163+
await github.rest.issues.addLabels({
164+
owner: context.repo.owner,
165+
repo: context.repo.repo,
166+
issue_number: issueNumber,
167+
labels: parsedLabels.labels_to_add
168+
});
169+
core.info(`Successfully applied labels to #${issueNumber}: ${parsedLabels.labels_to_add.join(', ')}`);
170+
}
171+
172+
// Remove labels
173+
if (parsedLabels.labels_to_remove && parsedLabels.labels_to_remove.length > 0) {
174+
core.info(`Removing labels from #${issueNumber}: ${parsedLabels.labels_to_remove.join(', ')}`)
175+
for (const label of parsedLabels.labels_to_remove) {
176+
try {
177+
await github.rest.issues.removeLabel({
178+
owner: context.repo.owner,
179+
repo: context.repo.repo,
180+
issue_number: issueNumber,
181+
name: label
182+
});
183+
core.info(`Successfully removed label from #${issueNumber}: ${label}`);
184+
} catch (error) {
185+
// Label might not exist on the issue, which is fine
186+
core.info(`Could not remove label ${label} from #${issueNumber}: ${error.message}`);
187+
}
188+
}
189+
}
190+
191+
- name: 'Post Issue Analysis Failure Comment'
192+
if: |-
193+
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
194+
env:
195+
ISSUE_NUMBER: '${{ github.event.issue.number }}'
196+
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
121197
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
122198
with:
123199
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
124200
script: |-
125201
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.'
202+
owner: context.repo.owner,
203+
repo: context.repo.repo,
204+
issue_number: parseInt(process.env.ISSUE_NUMBER),
205+
body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${process.env.RUN_URL}) for details.'
130206
})

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

Lines changed: 108 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,98 @@ 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+
and the labels to add and remove. For example:
124+
```
125+
[
126+
{
127+
"issue_number": 123,
128+
"labels_to_add": ["kind/bug", "priority/p2"],
129+
"labels_to_remove": ["status/needs-triage"]
130+
},
131+
{
132+
"issue_number": 456,
133+
"labels_to_add": ["kind/enhancement"],
134+
"labels_to_remove": []
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+
// Apply labels
180+
if (entry.labels_to_add && entry.labels_to_add.length > 0) {
181+
await github.rest.issues.addLabels({
182+
owner: context.repo.owner,
183+
repo: context.repo.repo,
184+
issue_number: issueNumber,
185+
labels: entry.labels_to_add
186+
});
187+
core.info(`Successfully applied labels to #${issueNumber}: ${entry.labels_to_add.join(', ')}`);
188+
}
189+
190+
// Remove labels
191+
if (entry.labels_to_remove && entry.labels_to_remove.length > 0) {
192+
core.info(`Removing labels from #${issueNumber}: ${entry.labels_to_remove.join(', ')}`);
193+
for (const label of entry.labels_to_remove) {
194+
try {
195+
await github.rest.issues.removeLabel({
196+
owner: context.repo.owner,
197+
repo: context.repo.repo,
198+
issue_number: issueNumber,
199+
name: label
200+
});
201+
core.info(`Successfully removed label from #${issueNumber}: ${label}`);
202+
} catch (error) {
203+
// Label might not exist on the issue, which is fine
204+
core.info(`Could not remove label ${label} from #${issueNumber}: ${error.message}`);
205+
}
206+
}
207+
}
208+
}

0 commit comments

Comments
 (0)