-
Notifications
You must be signed in to change notification settings - Fork 168
187 lines (167 loc) · 7.48 KB
/
agentic-ci-issue-triage.yml
File metadata and controls
187 lines (167 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: "Agentic CI: Repository Triage"
on:
schedule:
- cron: "0 10 * * 1" # every Monday at 10:00 UTC
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: read
concurrency:
group: agentic-ci-issue-triage
cancel-in-progress: true
jobs:
triage:
if: github.repository_owner == 'NVIDIA-NeMo'
runs-on: [self-hosted, agentic-ci]
timeout-minutes: 15
steps:
- name: Check required config
env:
AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }}
TRACKING_ISSUE: ${{ vars.ISSUE_TRIAGE_TRACKING_ISSUE }}
run: |
if [ -z "$AGENTIC_CI_MODEL" ]; then
echo "::error::AGENTIC_CI_MODEL variable is not set. Configure it in repo settings."
exit 1
fi
if [ -z "$TRACKING_ISSUE" ]; then
echo "::error::ISSUE_TRIAGE_TRACKING_ISSUE variable is not set. Create a pinned issue and set the variable."
exit 1
fi
- name: Checkout main
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: main
- name: Pre-flight checks
env:
ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }}
AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }}
run: |
if ! command -v claude &> /dev/null; then
echo "::error::claude CLI not found in PATH"
exit 1
fi
echo "Claude CLI version: $(claude --version 2>&1 || true)"
if [ -n "$ANTHROPIC_BASE_URL" ] && [ -n "$ANTHROPIC_API_KEY" ]; then
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 10 \
-X POST "${ANTHROPIC_BASE_URL}/v1/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-d "{\"model\":\"${AGENTIC_CI_MODEL}\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}")
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "::error::API pre-flight failed with HTTP ${HTTP_CODE}"
exit 1
fi
echo "API pre-flight passed (HTTP ${HTTP_CODE})"
fi
- name: Run issue triage recipe
env:
ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }}
AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }}
DISABLE_PROMPT_CACHING: "1"
GH_TOKEN: ${{ github.token }}
ISSUE_TRIAGE_TRACKING_ISSUE: ${{ vars.ISSUE_TRIAGE_TRACKING_ISSUE }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -o pipefail
rm -f /tmp/issue-triage-report.md /tmp/issue-triage-report-*.md /tmp/claude-triage-log.txt
RUNNER_CTX=$(cat .agents/recipes/_runner.md)
RECIPE_BODY=$(cat .agents/recipes/issue-triage/recipe.md \
| sed '1,/^---$/{ /^---$/,/^---$/d }')
PROMPT=$(printf '%s\n\n%s\n' "${RUNNER_CTX}" "${RECIPE_BODY}")
claude \
--model "$AGENTIC_CI_MODEL" \
-p "$PROMPT" \
--max-turns 30 \
--output-format text \
--verbose \
2>&1 | tee /tmp/claude-triage-log.txt || true
continue-on-error: true
- name: Fallback post if agent did not post
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TRACKING_ISSUE: ${{ vars.ISSUE_TRIAGE_TRACKING_ISSUE }}
run: |
# Collect report parts. Numbered files (multi-part) take precedence;
# fall back to the unsuffixed file for single-part runs.
shopt -s nullglob
PARTS=(/tmp/issue-triage-report-*.md)
shopt -u nullglob
if [ ${#PARTS[@]} -eq 0 ] && [ -s /tmp/issue-triage-report.md ]; then
PARTS=(/tmp/issue-triage-report.md)
fi
if [ ${#PARTS[@]} -eq 0 ]; then
echo "::warning::Triage report not created by agent."
exit 0
fi
mapfile -t PARTS < <(printf '%s\n' "${PARTS[@]}" | sort -V)
EXPECTED_PARTS=${#PARTS[@]}
# Skip fallback only if every expected part identity (i/N) appears
# in a today-dated bot comment. Identity-based, not count-based:
# a duplicate post of one part should not mask a missing other.
# The pre-`capture()` `test()` guard is required: jq `capture()`
# raises an error on non-matching input, which would truncate the
# stream if any unrelated today-dated bot comment lacks the
# triage marker (e.g. from another automated workflow).
TODAY=$(date -u +%Y-%m-%d)
if ! SEEN_PARTS=$(gh api "repos/${{ github.repository }}/issues/${TRACKING_ISSUE}/comments" \
--paginate \
--jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"${TODAY}\")) | select(.body | test(\"agentic-ci-issue-triage:[0-9]+/${EXPECTED_PARTS}\")) | .body | capture(\"agentic-ci-issue-triage:(?<i>[0-9]+)/${EXPECTED_PARTS}\") | .i" \
2>/dev/null | sort -u); then
echo "::warning::Could not inspect existing triage comments; treating all generated parts as missing."
SEEN_PARTS=""
fi
MISSING=()
for i in $(seq 1 "${EXPECTED_PARTS}"); do
if ! echo "${SEEN_PARTS}" | grep -qx "${i}"; then
MISSING+=("${i}")
fi
done
if [ ${#MISSING[@]} -eq 0 ]; then
echo "All ${EXPECTED_PARTS} parts already posted today (identities: $(echo "${SEEN_PARTS}" | tr '\n' ',' | sed 's/,$//')). Skipping fallback."
exit 0
fi
if [ -n "${SEEN_PARTS}" ]; then
echo "::warning::Posting only missing parts ${MISSING[*]} of ${EXPECTED_PARTS}; the agent already posted parts $(echo "${SEEN_PARTS}" | tr '\n' ',' | sed 's/,$//')."
fi
for i in "${MISSING[@]}"; do
PART="${PARTS[$((i-1))]}"
gh issue comment "$TRACKING_ISSUE" --body-file "$PART"
echo "Posted part ${i}: ${PART}"
done
- name: Write job summary
if: always()
shell: bash
run: |
shopt -s nullglob
PARTS=(/tmp/issue-triage-report-*.md)
shopt -u nullglob
if [ ${#PARTS[@]} -eq 0 ] && [ -s /tmp/issue-triage-report.md ]; then
PARTS=(/tmp/issue-triage-report.md)
fi
if [ ${#PARTS[@]} -gt 0 ]; then
mapfile -t PARTS < <(printf '%s\n' "${PARTS[@]}" | sort -V)
for PART in "${PARTS[@]}"; do
cat "${PART}" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
done
else
echo "No triage report was generated." >> "$GITHUB_STEP_SUMMARY"
fi
if [ -s "/tmp/claude-triage-log.txt" ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "<details><summary>Agent log</summary>" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
tail -100 /tmp/claude-triage-log.txt >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "</details>" >> "$GITHUB_STEP_SUMMARY"
fi