Assign issue to a team #185
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: "Assign issue to a team" | |
| on: | |
| issues: | |
| types: [opened, reopened] | |
| workflow_dispatch: | |
| inputs: | |
| issue_number: | |
| description: 'Issue number to triage' | |
| required: true | |
| type: number | |
| jobs: | |
| set_pending_label: | |
| if: github.event_name == 'issues' && github.event.issue.state == 'open' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Set pending label | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| labels: ["pending"] | |
| }); | |
| triage: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write # This is required for getting the required OIDC token from GitHub | |
| environment: | |
| name: main | |
| steps: | |
| - uses: DataDog/dd-octo-sts-action@acaa02eee7e3bb0839e4272dacb37b8f3b58ba80 # v1.0.3 | |
| id: octo-sts | |
| with: | |
| scope: DataDog/datadog-agent | |
| policy: self.assign-issue.label-issue | |
| - name: Get issue details | |
| id: get-issue | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| github-token: ${{ steps.octo-sts.outputs.token }} | |
| script: | | |
| let issue; | |
| if (context.eventName === 'workflow_dispatch') { | |
| // Fetch issue details when manually triggered | |
| const issueNumber = ${{ github.event.inputs.issue_number || 'null' }}; | |
| const response = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber | |
| }); | |
| issue = response.data; | |
| } else { | |
| // Use issue from event payload | |
| issue = context.payload.issue; | |
| } | |
| // Set outputs for use in later steps | |
| core.setOutput('title', issue.title); | |
| core.setOutput('number', issue.number); | |
| core.setOutput('author', issue.user.login); | |
| core.setOutput('url', issue.html_url); | |
| core.setOutput('body', issue.body || ''); | |
| return issue; | |
| - name: Checkout repository | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Intelligent Issue Triage | |
| id: claude | |
| uses: anthropics/claude-code-action@ac1a3207f3f00b4a37e2f3a6f0935733c7c64651 # v1.0.0 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ steps.octo-sts.outputs.token }} | |
| allowed_non_write_users: '*' # If not set the action will always use the event actor permissions. | |
| claude_args : --allowedTools "Read,Write,Bash(gh issue edit:*)" | |
| prompt: | | |
| # Datadog Agent - Intelligent Issue Triage System | |
| ## Context | |
| You are an expert triage system for the Datadog Agent repository. Your job is to analyze this GitHub issue and determine which team should handle it. You have full access to the Datadog Agent codebase and can use all available tools to analyze issues. | |
| ## Current Issue | |
| **Title:** ${{ steps.get-issue.outputs.title }} | |
| **Number:** #${{ steps.get-issue.outputs.number }} | |
| **Author:** @${{ steps.get-issue.outputs.author }} | |
| **URL:** ${{ steps.get-issue.outputs.url }} | |
| **Body:** | |
| ``` | |
| ${{ steps.get-issue.outputs.body }} | |
| ``` | |
| ## Analysis Protocol | |
| 1. **Extract Technical Keywords**: Parse issue for component names, error messages, config keys, file paths | |
| 2. **Codebase Search**: Use grep/find to locate relevant files and directories | |
| 3. **Ownership Analysis**: Check CODEOWNERS for team assignments | |
| 4. **Pattern Matching**: Look for similar historical issues and their team assignments | |
| 5. **Team Assignment**: Apply the most appropriate team label | |
| 6. **Documentation**: Comment on the issue explaining the triage decision | |
| ## Available Teams | |
| Extract all the team name labels from the .github/CODEOWNERS file. | |
| If the name of the team is `@DataDog/agent-devx` in the CODEOWNERS file, its associated label will be `team/agent-devx`. | |
| Generate all the label names using the example above. | |
| The github team name will be used to retrieve the slack channel once the team has been selected. | |
| ## Action Items | |
| 1. Perform thorough analysis using available tools | |
| 2. Select exactly one team label | |
| 3. Generate explanatory comment of the choice you did | |
| 4. If uncertain, apply `team/agent-devx` and request manual review | |
| 5. Apply the selected labels using the **Apply label** section below | |
| 6. Retrieve the slack channel associated to the selected team in the tasks/libs/pipeline/github_slack_map.yaml | |
| 7. Write TEAM, SLACK, CONFIDENCE and EXPLANATION in a local file claude.txt for the next step | |
| **IMPORTANT:** | |
| - For `team=`, provide ONLY the team name without "team/" prefix (e.g., "container-platform", not "team/container-platform") | |
| - Use HIGH confidence if you're very sure, MEDIUM if reasonably sure, LOW if uncertain | |
| - If you don't find a corresponding slack channel, use #agent-devx | |
| - Keep explanation under 150 characters | |
| ## Apply label | |
| Use gh issue edit to apply your selected labels | |
| DO NOT post any comments explaining your decision | |
| DO NOT communicate directly with users | |
| If no labels are clearly applicable, do not apply any labels | |
| ## Output Format | |
| At the end of your analysis, provide a summary in this exact format: | |
| TEAM:team_name_only | |
| SLACK:corresponding_slack_channel_from_the_map | |
| CONFIDENCE:HIGH|MEDIUM|LOW | |
| EXPLANATION:Brief explanation of why this team was chosen | |
| Make sure these three lines appear at the very end of your response. | |
| Write this exact same content in the local claude.txt file. | |
| Start your analysis now and ensure you create the GitHub Action outputs. | |
| - name: Extract label from file | |
| id: extract-label | |
| run: | | |
| if [ -f claude.txt ]; then | |
| TEAM=$(grep TEAM claude.txt | cut -d ':' -f2) | |
| echo "team=$TEAM" >> $GITHUB_OUTPUT | |
| SLACK=$(grep SLACK claude.txt | cut -d ':' -f2) | |
| echo "slack=$SLACK" >> $GITHUB_OUTPUT | |
| CONFIDENCE=$(grep CONFIDENCE claude.txt | cut -d ':' -f2) | |
| echo "confidence=$CONFIDENCE" >> $GITHUB_OUTPUT | |
| EXPLANATION=$(grep EXPLANATION claude.txt | cut -d ':' -f2) | |
| echo "explanation=$EXPLANATION" >> $GITHUB_OUTPUT | |
| else | |
| echo "team=unknown" >> $GITHUB_OUTPUT | |
| echo "slack=agent-devx" >> $GITHUB_OUTPUT | |
| echo "confidence=low" >> $GITHUB_OUTPUT | |
| echo "explanation=none" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Install dependencies | |
| run: npm install @slack/web-api | |
| - name: Send Slack Message | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| env: | |
| SLACK_TOKEN: ${{ secrets.SLACK_DATADOG_AGENT_BOT_TOKEN }} | |
| TEAM: ${{ steps.extract-label.outputs.team }} | |
| SLACK: ${{ steps.extract-label.outputs.slack }} | |
| CONFIDENCE: ${{ steps.extract-label.outputs.confidence }} | |
| EXPLANATION: ${{ steps.extract-label.outputs.explanation }} | |
| ISSUE_TITLE: "${{ steps.get-issue.outputs.title }}" | |
| ISSUE_NUMBER: "${{ steps.get-issue.outputs.number }}" | |
| ISSUE_URL: "${{ steps.get-issue.outputs.url }}" | |
| with: | |
| script: | | |
| const { WebClient } = require('@slack/web-api'); | |
| // Get issue details from environment (works for both event types) | |
| const issue = { | |
| title: process.env.ISSUE_TITLE, | |
| number: process.env.ISSUE_NUMBER, | |
| html_url: process.env.ISSUE_URL | |
| }; | |
| const teamName = process.env.TEAM; | |
| const channel = process.env.SLACK; | |
| const confidence = process.env.CONFIDENCE; | |
| const explanation = process.env.EXPLANATION; | |
| console.log(`Processing triage for issue #${issue.number} Team: ${teamName}`); | |
| // Send Slack notification (replicating assign_owner logic from tasks/issue.py) | |
| try { | |
| const slack = new WebClient(process.env.SLACK_TOKEN); | |
| // Create confidence emoji | |
| const confidenceEmojis = { | |
| 'HIGH': ':white_check_mark:', | |
| 'MEDIUM': ':warning:', | |
| 'LOW': ':question:' | |
| }; | |
| const confidenceEmoji = confidenceEmojis[confidence] || ':robot_face:'; | |
| // Create message (similar format to tasks/issue.py lines 37-43) | |
| let message = `:githubstatus_partial_outage: *New Community Issue* | |
| ${issue.title} <${issue.html_url}|${context.repo.repo}#${issue.number}> | |
| ${confidenceEmoji} *Auto-assigned* to \`team/${teamName}\` (Confidence: ${confidence}) | |
| *Analysis:* ${explanation} | |
| `; | |
| message += "\nYour team was assigned automatically using :claude: analysis.\nPlease redirect if the assignment is incorrect."; | |
| const response = await slack.chat.postMessage({ | |
| channel: channel, | |
| text: message | |
| }); | |
| console.log(`✅ Slack message sent to ${channel} (message ts: ${response.ts})`); | |
| } catch (error) { | |
| console.error(`❌ Failed to send Slack notification: ${error}`); | |
| // Try to send error notification to default channel | |
| try { | |
| const slack = new WebClient(process.env.SLACK_TOKEN); | |
| await slack.chat.postMessage({ | |
| channel: '#agent-devx-ops', | |
| text: `⚠️ Failed to send triage notification for issue ${issue.html_url}. Team: ${teamName}. Error: ${error.message}` | |
| }); | |
| } catch (fallbackError) { | |
| console.error('Failed to send fallback notification:', fallbackError); | |
| } | |
| // Don't fail the job for Slack errors - label was already applied | |
| console.log('Label was applied successfully, continuing despite Slack notification failure...'); | |
| } | |
| console.log('✅ Triage process completed'); |