From 98262d8fb29c05b202b229dd4479ae7ffe038da4 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 1 Oct 2025 13:38:25 +0100 Subject: [PATCH] ci: allow dispatching workflow triage via label (#20042) Allows creating a task for an issue if a label 'traiage' is set. Requires membership of the `coder` org to run. Manual workflow_dispatch: https://github.com/coder/coder/actions/runs/18158719999/job/51684512634 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/traiage.yaml | 85 ++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/.github/workflows/traiage.yaml b/.github/workflows/traiage.yaml index 615c5bec19..dc45f242db 100644 --- a/.github/workflows/traiage.yaml +++ b/.github/workflows/traiage.yaml @@ -1,6 +1,9 @@ name: AI Triage Automation on: + issues: + types: + - labeled workflow_dispatch: inputs: issue_url: @@ -32,6 +35,7 @@ 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 }} @@ -43,17 +47,58 @@ jobs: actions: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - persist-credentials: false - fetch-depth: 0 + - name: Get GitHub user ID + id: github-user-id + if: always() + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_EVENT_USER_ID: ${{ github.event.sender.id }} + GITHUB_EVENT_USER_LOGIN: ${{ github.event.sender.login }} + GH_TOKEN: ${{ github.token }} + run: | + # 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}" + 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}" + 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.github-user-id.outputs.github_username }} + GITHUB_USER_ID: ${{ steps.github-user-id.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: ${{ inputs.issue_url }} - GITHUB_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ github.token }} run: | issue_number="$(gh issue view "${ISSUE_URL}" --json number --jq '.number')" context_key="gh-${issue_number}" @@ -82,11 +127,9 @@ jobs: id: get-coder-username env: CODER_SESSION_TOKEN: ${{ secrets.TRAIAGE_CODER_SESSION_TOKEN }} - GITHUB_USER_ID: ${{ - (github.event_name == 'workflow_dispatch' && github.actor_id) - }} + GH_TOKEN: ${{ github.token }} + GITHUB_USER_ID: ${{ steps.github-user-id.outputs.github_user_id }} run: | - [[ -z "${GITHUB_USER_ID}" || "${GITHUB_USER_ID}" == "null" ]] && echo "No GitHub actor ID found" && exit 1 user_json=$( coder users list --github-user-id="${GITHUB_USER_ID}" --output=json ) @@ -94,13 +137,20 @@ jobs: [[ -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 }} - GITHUB_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} ISSUE_URL: ${{ inputs.issue_url }} PREFIX: ${{ inputs.prefix }} RUN_ID: ${{ github.run_id }} @@ -125,7 +175,11 @@ jobs: export TASK_NAME="${PREFIX}-${CONTEXT_KEY}-${RUN_ID}" echo "Creating task: $TASK_NAME" ./scripts/traiage.sh create - coder exp task status "${CODER_USERNAME}/$TASK_NAME" --watch + 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}" @@ -134,7 +188,11 @@ jobs: if: inputs.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 "${CODER_USERNAME}/$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}" @@ -145,8 +203,9 @@ jobs: 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 }} - GITHUB_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ github.token }} GITHUB_REPOSITORY: ${{ github.repository }} ISSUE_URL: ${{ inputs.issue_url }} TASK_NAME: ${{ steps.create-task.outputs.TASK_NAME }}