fix: resolve 4 bugs in devtrack #7647
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 Assignment Bot | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| issues: write | |
| jobs: | |
| assign: | |
| name: Auto-assign contributor | |
| runs-on: ubuntu-latest | |
| # Only issues — not PR comments | |
| if: github.event.issue.pull_request == null | |
| steps: | |
| - name: Handle assignment request | |
| uses: actions/github-script@v9 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const comment = context.payload.comment.body.toLowerCase().trim(); | |
| // Broad intent matching. The old exact-string list missed common | |
| // phrasings — e.g. "I'd love to work on this feature", "please | |
| // assign it to me", "/assign" — leaving contributors unassigned | |
| // even though the bot ran. These regexes tolerate those variants. | |
| const PATTERNS = [ | |
| /(^|\s)\/assign\b/, // slash command: /assign, /assign me | |
| /\bassign(ed|ing)?\b[\s\S]{0,40}\b(me|this|it|myself|to me)\b/, // "assign me", "assign this to me", "assign it to me" | |
| /\b(me|this|it|myself)\b[\s\S]{0,25}\bassign(ed)?\b/, // "can this be assigned to me" | |
| /\bwork(ing)?\s+on\s+(this|it)\b/, // "work on this", "working on it" | |
| /\b(want|like|love|willing|happy|keen|glad|wish|hoping)\s+to\s+(work|take|tackle|contribute|help|solve|fix|do)\b/, | |
| /\b(let me|i can|i'?ll|i will|i'?d|i would|i want|can i|may i|could i)\b[\s\S]{0,30}\b(work|take|tackle|handle|pick|solve|fix|contribute|do this)\b/, | |
| /\binterested\s+in\s+(work|contribut|solv|this|tak|fix)/, // "interested in working/contributing" | |
| /\b(take|tackle|handle|pick)\s+(this|it|up)\b/, // "take this", "pick it up" | |
| ]; | |
| if (!PATTERNS.some(re => re.test(comment))) return; | |
| // Skip bots | |
| if (context.payload.comment.user.type === 'Bot') return; | |
| const { owner, repo } = context.repo; | |
| const issueNumber = context.payload.issue.number; | |
| const commenter = context.payload.comment.user.login; | |
| // Fetch live issue state — do NOT use context.payload.issue which | |
| // is a stale webhook snapshot and causes a race condition where | |
| // concurrent comments both see 0 assignees and both get assigned. | |
| const { data: issue } = await github.rest.issues.get({ | |
| owner, repo, issue_number: issueNumber, | |
| }); | |
| // Re-check: skip if issue is now closed (may have changed since comment) | |
| if (issue.state !== 'open') return; | |
| // Check if issue is already assigned | |
| if (issue.assignees && issue.assignees.length > 0) { | |
| const assignees = issue.assignees.map(a => a.login); | |
| if (assignees.includes(commenter)) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: issueNumber, | |
| body: `@${commenter} You are already assigned to this issue. Open a PR when ready.`, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: issueNumber, | |
| body: `@${commenter} This issue is already taken by @${assignees.join(', @')}. Check other open issues!`, | |
| }); | |
| } | |
| return; | |
| } | |
| // NOTE: per-contributor issue limit is temporarily disabled | |
| // TODO: re-enable when needed by restoring the block below | |
| /* | |
| // Limit: max 2 open issues assigned per contributor | |
| const allAssigned = await github.paginate(github.rest.issues.listForRepo, { | |
| owner, repo, assignee: commenter, state: 'open', per_page: 50, | |
| }); | |
| const openIssueCount = allAssigned.filter(i => !i.pull_request).length; | |
| if (openIssueCount >= 2) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: issueNumber, | |
| body: `@${commenter} You already have ${openIssueCount} open issue(s) assigned. Complete those first before picking up more.`, | |
| }); | |
| return; | |
| } | |
| */ | |
| // Assign | |
| await github.rest.issues.addAssignees({ | |
| owner, repo, issue_number: issueNumber, | |
| assignees: [commenter], | |
| }); | |
| // Add gssoc:assigned label if not already present | |
| const currentLabels = issue.labels.map(l => l.name); | |
| if (!currentLabels.includes('gssoc:assigned')) { | |
| await github.rest.issues.addLabels({ | |
| owner, repo, issue_number: issueNumber, | |
| labels: ['gssoc:assigned'], | |
| }); | |
| } | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: issueNumber, | |
| body: `Assigned to @${commenter}! You have **7 days** to open a PR. No PR after 7 days = auto-unassigned so others can pick it up. Good luck!`, | |
| }); |