- Notifications
You must be signed in to change notification settings - Fork1k
AI Triage Automation#51
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
name:AI Triage Automation | |
on: | |
issues: | |
types: | |
-labeled | |
workflow_dispatch: | |
inputs: | |
issue_url: | |
description:"GitHub Issue URL to process" | |
required:true | |
type:string | |
template_name: | |
description:"Coder template to use for workspace" | |
required:true | |
default:"traiage" | |
type:string | |
template_preset: | |
description:"Template preset to use" | |
required:true | |
default:"Default" | |
type:string | |
prefix: | |
description:"Prefix for workspace name" | |
required:false | |
default:"traiage" | |
type:string | |
cleanup: | |
description:"Cleanup workspace after triage." | |
required:false | |
default:false | |
type:boolean | |
jobs: | |
traiage: | |
name:Triage GitHub Issue with Claude Code | |
runs-on:ubuntu-latest | |
if:github.event.label.name == 'traiage' || github.event_name == 'workflow_dispatch' | |
timeout-minutes:30 | |
env: | |
CODER_URL:${{ secrets.TRAIAGE_CODER_URL }} | |
CODER_SESSION_TOKEN:${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }} | |
permissions: | |
contents:read | |
issues:write | |
actions:write | |
steps: | |
# This is only required for testing locally using nektos/act, so leaving commented out. | |
# An alternative is to use a larger or custom image. | |
# - name: Install Github CLI | |
# id: install-gh | |
# run: | | |
# (type -p wget >/dev/null || (sudo apt update && sudo apt install wget -y)) \ | |
# && sudo mkdir -p -m 755 /etc/apt/keyrings \ | |
# && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ | |
# && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ | |
# && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ | |
# && sudo mkdir -p -m 755 /etc/apt/sources.list.d \ | |
# && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ | |
# && sudo apt update \ | |
# && sudo apt install gh -y | |
-name:Determine Inputs | |
id:determine-inputs | |
if:always() | |
env: | |
GITHUB_ACTOR:${{ github.actor }} | |
GITHUB_EVENT_ISSUE_HTML_URL:${{ github.event.issue.html_url }} | |
GITHUB_EVENT_NAME:${{ github.event_name }} | |
GITHUB_EVENT_USER_ID:${{ github.event.sender.id }} | |
GITHUB_EVENT_USER_LOGIN:${{ github.event.sender.login }} | |
INPUTS_CLEANUP:${{ inputs.cleanup || false }} | |
INPUTS_ISSUE_URL:${{ inputs.issue_url }} | |
INPUTS_TEMPLATE_NAME:${{ inputs.template_name || 'traiage' }} | |
INPUTS_TEMPLATE_PRESET:${{ inputs.template_preset || 'Default'}} | |
INPUTS_PREFIX:${{ inputs.prefix || 'traiage' }} | |
GH_TOKEN:${{ github.token }} | |
run:| | |
echo "Using template name: ${INPUTS_TEMPLATE_NAME}" | |
echo "template_name=${INPUTS_TEMPLATE_NAME}" >> "${GITHUB_OUTPUT}" | |
echo "Using template preset: ${INPUTS_TEMPLATE_PRESET}" | |
echo "template_preset=${INPUTS_TEMPLATE_PRESET}" >> "${GITHUB_OUTPUT}" | |
echo "Using prefix: ${INPUTS_PREFIX}" | |
echo "prefix=${INPUTS_PREFIX}" >> "${GITHUB_OUTPUT}" | |
echo "Using cleanup: ${INPUTS_CLEANUP}" | |
echo "cleanup=${INPUTS_CLEANUP}" >> "${GITHUB_OUTPUT}" | |
# For workflow_dispatch, use the actor who triggered it | |
# For issues events, use the issue author. | |
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
if ! GITHUB_USER_ID=$(gh api "users/${GITHUB_ACTOR}" --jq '.id'); then | |
echo "::error::Failed to get GitHub user ID for actor ${GITHUB_ACTOR}" | |
exit 1 | |
fi | |
echo "Using workflow_dispatch actor: ${GITHUB_ACTOR} (ID: ${GITHUB_USER_ID})" | |
echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}" | |
echo "github_username=${GITHUB_ACTOR}" >> "${GITHUB_OUTPUT}" | |
echo "Using issue URL: ${INPUTS_ISSUE_URL}" | |
echo "issue_url=${INPUTS_ISSUE_URL}" >> "${GITHUB_OUTPUT}" | |
exit 0 | |
elif [[ "${GITHUB_EVENT_NAME}" == "issues" ]]; then | |
GITHUB_USER_ID=${GITHUB_EVENT_USER_ID} | |
echo "Using issue author: ${GITHUB_EVENT_USER_LOGIN} (ID: ${GITHUB_USER_ID})" | |
echo "github_user_id=${GITHUB_USER_ID}" >> "${GITHUB_OUTPUT}" | |
echo "github_username=${GITHUB_EVENT_USER_LOGIN}" >> "${GITHUB_OUTPUT}" | |
echo "Using issue URL: ${GITHUB_EVENT_ISSUE_HTML_URL}" | |
echo "issue_url=${GITHUB_EVENT_ISSUE_HTML_URL}" >> "${GITHUB_OUTPUT}" | |
exit 0 | |
else | |
echo "::error::Unsupported event type: ${GITHUB_EVENT_NAME}" | |
exit 1 | |
fi | |
-name:Verify organization membership | |
env: | |
GITHUB_ORG:${{ github.repository_owner }} | |
GH_TOKEN:${{ github.token }} | |
GITHUB_USERNAME:${{ steps.determine-inputs.outputs.github_username }} | |
GITHUB_USER_ID:${{ steps.determine-inputs.outputs.github_user_id }} | |
run:| | |
# Check if the actor is a member of the organization | |
if ! gh api "orgs/${GITHUB_ORG}/members/${GITHUB_USERNAME}" --silent 2>/dev/null; then | |
echo "::error title=Access Denied::User ${GITHUB_USERNAME} is not a member of the ${GITHUB_ORG} organization" | |
echo "::error::You must be a member of the ${GITHUB_ORG} GitHub organization to run this workflow." | |
exit 1 | |
fi | |
echo "::notice::User ${GITHUB_USERNAME} verified as member of ${GITHUB_ORG} organization" | |
-name:Extract context key from issue | |
id:extract-context | |
env: | |
ISSUE_URL:${{ steps.determine-inputs.outputs.issue_url }} | |
GH_TOKEN:${{ github.token }} | |
run:| | |
issue_number="$(gh issue view "${ISSUE_URL}" --json number --jq '.number')" | |
context_key="gh-${issue_number}" | |
echo "context_key=${context_key}" >> "${GITHUB_OUTPUT}" | |
echo "CONTEXT_KEY=${context_key}" >> "${GITHUB_ENV}" | |
-name:Download and install Coder binary | |
shell:bash | |
env: | |
CODER_URL:${{ secrets.TRAIAGE_CODER_URL }} | |
run:| | |
if [ "${{ runner.arch }}" == "ARM64" ]; then | |
ARCH="arm64" | |
else | |
ARCH="amd64" | |
fi | |
mkdir -p "${HOME}/.local/bin" | |
curl -fsSL --compressed "$CODER_URL/bin/coder-linux-${ARCH}" -o "${HOME}/.local/bin/coder" | |
chmod +x "${HOME}/.local/bin/coder" | |
export PATH="$HOME/.local/bin:$PATH" | |
coder version | |
coder whoami | |
echo "$HOME/.local/bin" >> "${GITHUB_PATH}" | |
-name:Get Coder username from GitHub actor | |
id:get-coder-username | |
env: | |
CODER_SESSION_TOKEN:${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }} | |
GH_TOKEN:${{ github.token }} | |
GITHUB_USER_ID:${{ steps.determine-inputs.outputs.github_user_id }} | |
run:| | |
user_json=$( | |
coder users list --github-user-id="${GITHUB_USER_ID}" --output=json | |
) | |
coder_username=$(jq -r 'first | .username' <<< "$user_json") | |
[[ -z "${coder_username}" || "${coder_username}" == "null" ]] && echo "No Coder user with GitHub user ID ${GITHUB_USER_ID} found" && exit 1 | |
echo "coder_username=${coder_username}" >> "${GITHUB_OUTPUT}" | |
-name:Checkout repository | |
uses:actions/checkout@v4 | |
with: | |
persist-credentials:false | |
fetch-depth:0 | |
# TODO(Cian): this is a good use-case for 'recipes' | |
-name:Create Coder task | |
id:create-task | |
env: | |
CODER_USERNAME:${{ steps.get-coder-username.outputs.coder_username }} | |
CONTEXT_KEY:${{ steps.extract-context.outputs.context_key }} | |
GH_TOKEN:${{ github.token }} | |
GITHUB_REPOSITORY:${{ github.repository }} | |
ISSUE_URL:${{ steps.determine-inputs.outputs.issue_url }} | |
PREFIX:${{ steps.determine-inputs.outputs.prefix }} | |
RUN_ID:${{ github.run_id }} | |
TEMPLATE_NAME:${{ steps.determine-inputs.outputs.template_name }} | |
TEMPLATE_PARAMETERS:${{ secrets.TRAIAGE_TEMPLATE_PARAMETERS }} | |
TEMPLATE_PRESET:${{ steps.determine-inputs.outputs.template_preset }} | |
run:| | |
# Fetch issue description using `gh` CLI | |
issue_description=$(gh issue view "${ISSUE_URL}") | |
# Write a prompt to PROMPT_FILE | |
PROMPT=$(cat <<EOF | |
Analyze the below GitHub issue description, understand the root cause, and make appropriate changes to resolve the issue. | |
ISSUE URL: ${ISSUE_URL} | |
ISSUE DESCRIPTION BELOW: | |
${issue_description} | |
EOF | |
) | |
export PROMPT | |
export TASK_NAME="${PREFIX}-${CONTEXT_KEY}-${RUN_ID}" | |
echo "Creating task: $TASK_NAME" | |
./scripts/traiage.sh create | |
if [[ "${ISSUE_URL}" == "https://github.com/${GITHUB_REPOSITORY}"* ]]; then | |
gh issue comment "${ISSUE_URL}" --body "Task created: ${TASK_NAME}" --create-if-none --edit-last | |
else | |
echo "Skipping comment on other repo." | |
fi | |
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_OUTPUT}" | |
echo "TASK_NAME=${CODER_USERNAME}/${TASK_NAME}" >> "${GITHUB_ENV}" | |
-name:Create and upload archive | |
id:create-archive | |
if:${{ steps.determine-inputs.outputs.cleanup }} | |
env: | |
BUCKET_PREFIX:"gs://coder-traiage-outputs/traiage" | |
CODER_USERNAME:${{ steps.get-coder-username.outputs.coder_username }} | |
TASK_NAME:${{ steps.create-task.outputs.TASK_NAME }} | |
run:| | |
echo "Waiting for task to complete..." | |
coder exp task status "${TASK_NAME}" --watch | |
echo "Creating archive for workspace: ${TASK_NAME}" | |
./scripts/traiage.sh archive | |
echo "archive_url=${BUCKET_PREFIX%%/}/$TASK_NAME.tar.gz" >> "${GITHUB_OUTPUT}" | |
-name:Generate a summary of the changes and post a comment on GitHub. | |
id:generate-summary | |
if:${{ steps.determine-inputs.outputs.cleanup }} | |
env: | |
ARCHIVE_URL:${{ steps.create-archive.outputs.archive_url }} | |
BUCKET_PREFIX:"gs://coder-traiage-outputs/traiage" | |
CODER_USERNAME:${{ steps.get-coder-username.outputs.coder_username }} | |
CONTEXT_KEY:${{ steps.extract-context.outputs.context_key }} | |
GH_TOKEN:${{ github.token }} | |
GITHUB_REPOSITORY:${{ github.repository }} | |
ISSUE_URL:${{ steps.determine-inputs.outputs.issue_url }} | |
TASK_NAME:${{ steps.create-task.outputs.TASK_NAME }} | |
run:| | |
SUMMARY_FILE=$(mktemp) | |
trap 'rm -f "${SUMMARY_FILE}"' EXIT | |
AUTO_SUMMARY=$(./scripts/traiage.sh summary) | |
{ | |
echo "## TrAIage Results" | |
echo "- **Issue URL:** ${ISSUE_URL}" | |
echo "- **Context Key:** ${CONTEXT_KEY}" | |
echo "- **Workspace:** ${TASK_NAME}" | |
echo "- **Archive URL:** ${ARCHIVE_URL}" | |
echo | |
echo "${AUTO_SUMMARY}" | |
echo | |
echo "To fetch the output to your own workspace:" | |
echo | |
echo '```bash' | |
echo "BUCKET_PREFIX=${BUCKET_PREFIX} TASK_NAME=${TASK_NAME} ./scripts/traiage.sh resume" | |
echo '```' | |
echo | |
} >> "${SUMMARY_FILE}" | |
if [[ "${ISSUE_URL}" == "https://github.com/${GITHUB_REPOSITORY}"* ]]; then | |
gh issue comment "${ISSUE_URL}" --body-file "${SUMMARY_FILE}" --create-if-none --edit-last | |
else | |
echo "Skipping comment on other repo." | |
fi | |
cat "${SUMMARY_FILE}" >> "${GITHUB_STEP_SUMMARY}" | |
-name:Cleanup task | |
if:steps.determine-inputs.outputs.cleanup && steps.create-task.outputs.TASK_NAME != '' && steps.create-archive.outputs.archive_url != '' | |
run:| | |
echo "Cleaning up task: $TASK_NAME" | |
./scripts/traiage.sh delete ||true |