Issue Handler #2246
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: Issue Handler | |
| # Classifies and responds to issues — but ONLY from Deepgram org members, | |
| # or external issues that an org member has explicitly approved by applying | |
| # the type:suggestion label. | |
| # | |
| # External issues with no label are completely invisible to this workflow. | |
| # No response is posted, no tokens are consumed. An org member must manually | |
| # find it, review it, and apply type:suggestion before anything happens. | |
| # | |
| # @deepgram-robot in an issue → this workflow (classify, respond, ask questions) | |
| # @deepgram-robot in a PR → engineering.yml (code changes, repo ops) | |
| on: | |
| issues: | |
| types: [opened, edited, labeled, reopened] | |
| issue_comment: | |
| types: [created] | |
| schedule: | |
| - cron: '*/5 * * * *' | |
| workflow_dispatch: | |
| jobs: | |
| handle: | |
| name: Handle issue | |
| if: | | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' || | |
| github.event_name == 'issues' || | |
| (github.event_name == 'issue_comment' && | |
| !contains(github.event.issue.html_url, '/pull/') && | |
| contains(github.event.comment.body, '@deepgram-robot')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| concurrency: | |
| group: issue-handler-${{ github.event.issue.number || 'sweep' }} | |
| cancel-in-progress: false | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| # ------------------------------------------------------------------ | |
| # Resolve which issue to handle, and verify it's eligible. | |
| # | |
| # Eligibility rules (all paths): | |
| # - Issue author is a Deepgram org member, OR | |
| # - Issue carries the type:suggestion label (org member approved it) | |
| # | |
| # External issues with neither condition are silently skipped. | |
| # No comment is posted. No tokens are consumed. | |
| # ------------------------------------------------------------------ | |
| - name: Resolve issue | |
| id: issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| EVENT="${{ github.event_name }}" | |
| if [ "$EVENT" = "issues" ] || [ "$EVENT" = "issue_comment" ]; then | |
| NUMBER="${{ github.event.issue.number }}" | |
| # @deepgram-robot mention: gate on the commenter being an org member | |
| if [ "$EVENT" = "issue_comment" ]; then | |
| COMMENTER="${{ github.event.comment.user.login }}" | |
| if [[ "$COMMENTER" == *"[bot]"* ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0 | |
| fi | |
| http_line=$(gh api "orgs/deepgram/members/${COMMENTER}" -i 2>/dev/null | head -1) | |
| if ! echo "$http_line" | grep -q "204"; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "$COMMENTER is not a Deepgram org member — ignoring" | |
| exit 0 | |
| fi | |
| fi | |
| # Issue event: check author org membership OR type:suggestion label | |
| issue_json=$(gh issue view "$NUMBER" --repo "${{ github.repository }}" --json author,labels) | |
| author=$(echo "$issue_json" | python3 -c "import json,sys; print(json.load(sys.stdin)['author']['login'])") | |
| has_label=$(echo "$issue_json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('true' if any(l['name']=='type:suggestion' for l in d['labels']) else 'false')") | |
| if [[ "$author" != *"[bot]"* ]] && [[ "$has_label" != "true" ]]; then | |
| http_line=$(gh api "orgs/deepgram/members/${author}" -i 2>/dev/null | head -1) | |
| if ! echo "$http_line" | grep -q "204"; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Issue #${NUMBER} author '${author}' is not a Deepgram org member and has no label approval — skipping" | |
| exit 0 | |
| fi | |
| fi | |
| echo "number=$NUMBER" >> "$GITHUB_OUTPUT" | |
| else | |
| # Cron/dispatch sweep: pick up any open issue that hasn't had a | |
| # robot-reply yet. find_unhandled_issue.py enforces org membership | |
| # for issues without type:suggestion, so external issues still need | |
| # explicit approval before getting a response. | |
| NUMBER=$(gh issue list \ | |
| --repo "${{ github.repository }}" \ | |
| --state open \ | |
| --limit 50 \ | |
| --json number \ | |
| --jq '.[].number' | python3 .github/scripts/find_unhandled_issue.py \ | |
| --repo "${{ github.repository }}" \ | |
| --org deepgram 2>/dev/null || true) | |
| if [ -z "$NUMBER" ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "No eligible issues to handle" | |
| else | |
| echo "number=$NUMBER" >> "$GITHUB_OUTPUT" | |
| echo "Processing issue #$NUMBER" | |
| fi | |
| fi | |
| - name: Skip if nothing to handle | |
| if: steps.issue.outputs.skip == 'true' | |
| run: echo "Skipping — no eligible issue." | |
| - name: Checkout | |
| if: steps.issue.outputs.skip != 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Handle issue | |
| if: steps.issue.outputs.skip != 'true' | |
| uses: anthropics/claude-code-action@39431830520a7ae6c0c572f11a7707e7043325e3 # v1.0.95 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| mode: agent | |
| model: claude-sonnet-4-6 | |
| allowed_tools: "Bash" | |
| timeout_minutes: 8 | |
| direct_prompt: | | |
| You are the issue handler for the deepgram/dx-examples repository — a collection | |
| of working code examples showing Deepgram integrations. You represent the Deepgram | |
| developer experience team. | |
| Read the full issue: | |
| ```bash | |
| gh issue view ${{ steps.issue.outputs.number }} \ | |
| --repo ${{ github.repository }} \ | |
| --json number,title,body,labels,comments,author | |
| ``` | |
| **If `<!-- robot-reply -->` is in the last comment and no new comments appear | |
| after it — the issue is already handled. Do nothing and exit.** | |
| Otherwise read everything and respond. You do not need to rigidly classify — | |
| read it as a person and respond naturally: | |
| - Example or modification request → acknowledge, confirm you understand what | |
| they're after. If `type:suggestion` is not already on the issue, apply it: | |
| `gh issue edit ${{ steps.issue.outputs.number }} --repo ${{ github.repository }} --add-label "type:suggestion"` | |
| Then tell them it's queued for building. | |
| - Something broken in an existing example → acknowledge, ask for specifics if | |
| needed (SDK version, error message, language). Don't over-ask — if the issue | |
| already has everything, confirm it's noted. | |
| - A question → answer it directly. Look at relevant example code if helpful. | |
| - Vague or incomplete → ask one or two specific questions. Be precise about | |
| what you need — don't say "please provide more info". | |
| End every response with `<!-- robot-reply -->` on its own line (hidden marker): | |
| ```bash | |
| gh issue comment ${{ steps.issue.outputs.number }} \ | |
| --repo ${{ github.repository }} \ | |
| --body "YOUR RESPONSE | |
| <!-- robot-reply -->" | |
| ``` | |
| Be concise. Be specific. Sound like a developer, not a support bot. | |
| Do not start with "Great question!" or similar filler. | |
| ⛔ Do not modify any files. Do not open PRs. Do not merge anything. | |
| ⛔ Do not modify anything under .github/, context7.json, or renovate.json. |