Skip to main content
Glama
claude-commands.yml11.9 kB
name: Claude Commands (Issues Only) # This workflow handles @claude mentions on ISSUES only. # PR reviews are handled by separate workflows (claude-pr-review-labeled.yml, etc.) on: issue_comment: types: [created] issues: types: [opened, edited, assigned] concurrency: group: claude-commands-${{ github.event.issue.number || github.run_id }} cancel-in-progress: false permissions: contents: write pull-requests: write issues: write actions: read jobs: handle: # Only run on issues (not PRs) with @claude mention # Note: issue_comment fires for both issues AND PRs, so we filter PRs out if: | ( github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'issues' && !github.event.issue.pull_request && ( contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude') ) ) runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@v4 with: sparse-checkout: | scripts/workflows/claude/ sparse-checkout-cone-mode: false - name: Classify command id: classify run: | node scripts/workflows/claude/parse-command.mjs > classify.json cat classify.json should_run=$(jq -r '.shouldRun // false' classify.json) command_type=$(jq -r '.command.type // ""' classify.json) is_maintainer=$(jq -r '.isMaintainer // false' classify.json) issue=$(jq -r '.issue // ""' classify.json) actor=$(jq -r '.actor // ""' classify.json) association=$(jq -r '.association // ""' classify.json) echo "should_run=$should_run" >> "$GITHUB_OUTPUT" echo "command_type=$command_type" >> "$GITHUB_OUTPUT" echo "is_maintainer=$is_maintainer" >> "$GITHUB_OUTPUT" echo "issue=$issue" >> "$GITHUB_OUTPUT" echo "actor=$actor" >> "$GITHUB_OUTPUT" echo "association=$association" >> "$GITHUB_OUTPUT" env: CLAUDE_TRIGGER_PHRASE: '@claude' - name: Skip non-command events if: steps.classify.outputs.should_run != 'true' run: echo 'No @claude command detected.' - name: Skip review commands (handled by PR review workflows) if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.command_type == 'review' run: echo 'Review command on issue - skipping (use on PRs for review).' - name: Enforce maintainer access if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.command_type != 'review' run: | if [ "${{ steps.classify.outputs.is_maintainer }}" != "true" ]; then echo '::notice::Only maintainers may trigger Claude automation.' exit 1 fi - name: Full checkout for command execution if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.is_maintainer == 'true' && steps.classify.outputs.command_type != 'review' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Fetch issue context if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.is_maintainer == 'true' && steps.classify.outputs.command_type != 'review' id: context uses: actions/github-script@v7 with: script: | const issueNumber = '${{ steps.classify.outputs.issue }}'; if (!issueNumber) { core.setOutput('issue_title', ''); core.setOutput('issue_body', ''); core.setOutput('comment_body', ''); core.setOutput('recent_comments', ''); core.setOutput('previous_analysis', ''); return; } // Fetch issue details const { data: issue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber }); // Get the triggering comment const commentId = context.payload.comment?.id; let commentBody = ''; if (commentId) { const { data: comment } = await github.rest.issues.getComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: commentId }); commentBody = comment.body || ''; } // Fetch all comments for context const { data: allComments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, per_page: 100 }); // Find most recent Claude response (for follow-up context) const claudeResponses = allComments .filter(c => c.body.includes('<!-- claude-command-response -->')) .reverse(); // Most recent first const previousAnalysis = claudeResponses.length > 0 ? claudeResponses[0].body.replace('<!-- claude-command-response -->\n', '').slice(0, 8000) : ''; // Get recent user comments (last 5, excluding triggering comment and bot comments) const recentComments = allComments .filter(c => c.id !== commentId) // Exclude triggering comment .filter(c => c.user.type !== 'Bot') // Exclude bot comments .filter(c => !c.body.includes('<!-- claude-command-response -->')) // Exclude previous Claude responses .slice(-5) // Get last 5 user comments .map(c => ({ author: c.user.login, created_at: c.created_at, body: c.body.slice(0, 2000) // Limit each comment to 2000 chars })); const recentCommentsFormatted = recentComments.length > 0 ? recentComments.map(c => `**@${c.author}** (${new Date(c.created_at).toISOString().split('T')[0]}):\n${c.body}\n` ).join('\n---\n\n') : ''; // Parse issue body for related issues (e.g., #42, #100) const issueRefPattern = /#(\d+)/g; const referencedIssueNumbers = [...(issue.body || '').matchAll(issueRefPattern)] .map(match => parseInt(match[1])) .filter(num => num !== parseInt(issueNumber)) // Exclude self-reference .slice(0, 3); // Limit to first 3 related issues // Fetch related issues (title, state, first 500 chars of body) const relatedIssues = []; for (const relatedNum of referencedIssueNumbers) { try { const { data: relatedIssue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: relatedNum }); relatedIssues.push({ number: relatedNum, title: relatedIssue.title, state: relatedIssue.state, body: (relatedIssue.body || '').slice(0, 500) // Limit to 500 chars }); } catch (error) { core.warning(`Failed to fetch related issue #${relatedNum}: ${error.message}`); } } const relatedIssuesFormatted = relatedIssues.length > 0 ? relatedIssues.map(ri => `**#${ri.number}** (${ri.state}): ${ri.title}\n${ri.body}${ri.body.length >= 500 ? '...' : ''}` ).join('\n\n---\n\n') : ''; core.setOutput('issue_title', issue.title || ''); core.setOutput('issue_body', issue.body || ''); core.setOutput('comment_body', commentBody); core.setOutput('recent_comments', recentCommentsFormatted); core.setOutput('previous_analysis', previousAnalysis); core.setOutput('related_issues', relatedIssuesFormatted); core.info(`Fetched context for issue #${issueNumber}: ${issue.title}`); core.info(`Included ${recentComments.length} recent comments for context`); if (previousAnalysis) { core.info(`Found previous Claude analysis (${previousAnalysis.length} chars) - this is a follow-up`); } if (relatedIssues.length > 0) { core.info(`Found ${relatedIssues.length} related issue(s): ${relatedIssues.map(ri => `#${ri.number}`).join(', ')}`); } # Use default Claude Code Action behavior - it handles commenting automatically with track_progress - name: Claude Code Action (Default Behavior) if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.is_maintainer == 'true' && steps.classify.outputs.command_type != 'review' uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ github.token }} track_progress: true # No --max-turns = runs until completion (default action behavior) # Full tool access for implementation claude_args: >- --model claude-sonnet-4-5 --allowedTools "Read,Edit,Write,Bash(git:*),Bash(gh:*),Bash(npm:*),Bash(npx:*),Bash(node:*),Bash(rg:*),Bash(fd:*),Bash(cat:*),Bash(ls:*),Bash(head:*),Bash(tail:*),Bash(wc:*)" prompt: | You are Claude acting via GitHub Actions for the Attio MCP Server repository. ## Context - **Issue**: #${{ steps.classify.outputs.issue }} - **Issue Title**: ${{ steps.context.outputs.issue_title }} - **User Request**: ${{ steps.context.outputs.comment_body }} **Issue Description**: ${{ steps.context.outputs.issue_body }} **Recent Discussion**: ${{ steps.context.outputs.recent_comments || 'No recent comments' }} **Related Issues**: ${{ steps.context.outputs.related_issues || 'None referenced' }} ## Your Task Follow the user's request above. If they ask you to implement something: 1. **Read AGENTS.md first** - it contains project conventions and standards 2. **Create a feature branch**: `git checkout -b feature/issue-${{ steps.classify.outputs.issue }}-description` 3. **Implement the changes** following project standards 4. **Run quality gates**: - `npm run test:offline` (fast unit tests) - `npm run fix:all` (lint/format) - `npx tsc --noEmit` (TypeScript check) 5. **Commit with conventional format**: `Type: Description #${{ steps.classify.outputs.issue }}` 6. **Push and create a PR**: `gh pr create --title "Type: Description" --body "Fixes #${{ steps.classify.outputs.issue }}"` If they ask for analysis/advice, provide helpful guidance without implementing. ## Project Standards (from AGENTS.md) - Commit format: `Type: Description #issue-number` where Type = Feature|Fix|Docs|Refactor|Test|Chore - Use `@/` path aliases instead of relative imports - Run `npm run test:offline` for fast validation - ESLint warnings threshold: ≤1030 ## Constraints - Never expose secrets or tokens - Ask for clarification if the request is ambiguous - Report any issues or blockers you encounter - name: Summarize skip if: steps.classify.outputs.should_run != 'true' run: echo 'Workflow exited without invoking Claude.'

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server