1
1
name :AI Triage Automation
2
2
3
3
on :
4
+ issues :
5
+ types :
6
+ -labeled
4
7
workflow_dispatch :
5
8
inputs :
6
9
issue_url :
32
35
traiage :
33
36
name :Triage GitHub Issue with Claude Code
34
37
runs-on :ubuntu-latest
38
+ if :github.event.label.name == 'traiage' || github.event_name == 'workflow_dispatch'
35
39
timeout-minutes :30
36
40
env :
37
41
CODER_URL :${{ secrets.TRAIAGE_CODER_URL }}
@@ -43,17 +47,58 @@ jobs:
43
47
actions :write
44
48
45
49
steps :
46
- -name :Checkout repository
47
- uses :actions/checkout@v4
48
- with :
49
- persist-credentials :false
50
- fetch-depth :0
50
+ -name :Get GitHub user ID
51
+ id :github-user-id
52
+ if :always()
53
+ env :
54
+ GITHUB_ACTOR :${{ github.actor }}
55
+ GITHUB_EVENT_NAME :${{ github.event_name }}
56
+ GITHUB_EVENT_USER_ID :${{ github.event.sender.id }}
57
+ GITHUB_EVENT_USER_LOGIN :${{ github.event.sender.login }}
58
+ GH_TOKEN :${{ github.token }}
59
+ run :|
60
+ # For workflow_dispatch, use the actor who triggered it
61
+ # For issues events, use the issue author
62
+ if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
63
+ if ! GITHUB_USER_ID=$(gh api "users/${GITHUB_ACTOR}" --jq '.id'); then
64
+ echo "::error::Failed to get GitHub user ID for actor ${GITHUB_ACTOR}"
65
+ exit 1
66
+ fi
67
+ echo "Using workflow_dispatch actor: ${GITHUB_ACTOR} (ID: ${GITHUB_USER_ID})"
68
+ echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}"
69
+ echo "github_username=${GITHUB_ACTOR}" >> "${GITHUB_OUTPUT}"
70
+ exit 0
71
+ elif [[ "${GITHUB_EVENT_NAME}" == "issues" ]]; then
72
+ GITHUB_USER_ID=${GITHUB_EVENT_USER_ID}
73
+ echo "Using issue author: ${GITHUB_EVENT_USER_LOGIN} (ID: ${GITHUB_USER_ID})"
74
+ echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}"
75
+ echo "github_username=${GITHUB_EVENT_USER_LOGIN}" >> "${GITHUB_OUTPUT}"
76
+ exit 0
77
+ else
78
+ echo "::error::Unsupported event type: ${GITHUB_EVENT_NAME}"
79
+ exit 1
80
+ fi
81
+
82
+ -name :Verify organization membership
83
+ env :
84
+ GITHUB_ORG :${{ github.repository_owner }}
85
+ GH_TOKEN :${{ github.token }}
86
+ GITHUB_USERNAME :${{ steps.github-user-id.outputs.github_username }}
87
+ GITHUB_USER_ID :${{ steps.github-user-id.outputs.github_user_id }}
88
+ run :|
89
+ # Check if the actor is a member of the organization
90
+ if ! gh api "orgs/${GITHUB_ORG}/members/${GITHUB_USERNAME}" --silent 2>/dev/null; then
91
+ echo "::error title=Access Denied::User ${GITHUB_USERNAME} is not a member of the ${GITHUB_ORG} organization"
92
+ echo "::error::You must be a member of the ${GITHUB_ORG} GitHub organization to run this workflow."
93
+ exit 1
94
+ fi
95
+ echo "::notice::User ${GITHUB_USERNAME} verified as member of ${GITHUB_ORG} organization"
51
96
52
97
-name :Extract context key from issue
53
98
id :extract-context
54
99
env :
55
100
ISSUE_URL :${{ inputs.issue_url }}
56
- GITHUB_TOKEN :${{ github.token }}
101
+ GH_TOKEN :${{ github.token }}
57
102
run :|
58
103
issue_number="$(gh issue view "${ISSUE_URL}" --json number --jq '.number')"
59
104
context_key="gh-${issue_number}"
@@ -82,25 +127,30 @@ jobs:
82
127
id :get-coder-username
83
128
env :
84
129
CODER_SESSION_TOKEN :${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }}
85
- GITHUB_USER_ID :${{
86
- (github.event_name == 'workflow_dispatch' && github.actor_id)
87
- }}
130
+ GH_TOKEN :${{ github.token }}
131
+ GITHUB_USER_ID :${{ steps.github-user-id.outputs.github_user_id }}
88
132
run :|
89
- [[ -z "${GITHUB_USER_ID}" || "${GITHUB_USER_ID}" == "null" ]] && echo "No GitHub actor ID found" && exit 1
90
133
user_json=$(
91
134
coder users list --github-user-id="${GITHUB_USER_ID}" --output=json
92
135
)
93
136
coder_username=$(jq -r 'first | .username' <<< "$user_json")
94
137
[[ -z "${coder_username}" || "${coder_username}" == "null" ]] && echo "No Coder user with GitHub user ID ${GITHUB_USER_ID} found" && exit 1
95
138
echo "coder_username=${coder_username}" >> "${GITHUB_OUTPUT}"
96
139
140
+ -name :Checkout repository
141
+ uses :actions/checkout@v4
142
+ with :
143
+ persist-credentials :false
144
+ fetch-depth :0
145
+
97
146
# TODO(Cian): this is a good use-case for 'recipes'
98
147
-name :Create Coder task
99
148
id :create-task
100
149
env :
101
150
CODER_USERNAME :${{ steps.get-coder-username.outputs.coder_username }}
102
151
CONTEXT_KEY :${{ steps.extract-context.outputs.context_key }}
103
- GITHUB_TOKEN :${{ github.token }}
152
+ GH_TOKEN :${{ github.token }}
153
+ GITHUB_REPOSITORY :${{ github.repository }}
104
154
ISSUE_URL :${{ inputs.issue_url }}
105
155
PREFIX :${{ inputs.prefix }}
106
156
RUN_ID :${{ github.run_id }}
@@ -125,7 +175,11 @@ jobs:
125
175
export TASK_NAME="${PREFIX}-${CONTEXT_KEY}-${RUN_ID}"
126
176
echo "Creating task: $TASK_NAME"
127
177
./scripts/traiage.sh create
128
- coder exp task status "${CODER_USERNAME}/$TASK_NAME" --watch
178
+ if [[ "${ISSUE_URL}" == "https://github.com/${GITHUB_REPOSITORY}"* ]]; then
179
+ gh issue comment "${ISSUE_URL}" --body "Task created: ${TASK_NAME}" --create-if-none --edit-last
180
+ else
181
+ echo "Skipping comment on other repo."
182
+ fi
129
183
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_OUTPUT}"
130
184
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_ENV}"
131
185
@@ -134,7 +188,11 @@ jobs:
134
188
if :inputs.cleanup
135
189
env :
136
190
BUCKET_PREFIX :" gs://coder-traiage-outputs/traiage"
191
+ CODER_USERNAME :${{ steps.get-coder-username.outputs.coder_username }}
192
+ TASK_NAME :${{ steps.create-task.outputs.TASK_NAME }}
137
193
run :|
194
+ echo "Waiting for task to complete..."
195
+ coder exp task status "${CODER_USERNAME}/$TASK_NAME" --watch
138
196
echo "Creating archive for workspace: $TASK_NAME"
139
197
./scripts/traiage.sh archive
140
198
echo "archive_url=${BUCKET_PREFIX%%/}/$TASK_NAME.tar.gz" >> "${GITHUB_OUTPUT}"
@@ -145,8 +203,9 @@ jobs:
145
203
env :
146
204
ARCHIVE_URL :${{ steps.create-archive.outputs.archive_url }}
147
205
BUCKET_PREFIX :" gs://coder-traiage-outputs/traiage"
206
+ CODER_USERNAME :${{ steps.get-coder-username.outputs.coder_username }}
148
207
CONTEXT_KEY :${{ steps.extract-context.outputs.context_key }}
149
- GITHUB_TOKEN :${{ github.token }}
208
+ GH_TOKEN :${{ github.token }}
150
209
GITHUB_REPOSITORY :${{ github.repository }}
151
210
ISSUE_URL :${{ inputs.issue_url }}
152
211
TASK_NAME :${{ steps.create-task.outputs.TASK_NAME }}