Skip to main content
Glama
claude-commands.ymlβ€’21.4 kB
name: Claude Commands on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, edited, assigned] concurrency: group: claude-commands-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} cancel-in-progress: false permissions: contents: write pull-requests: write issues: write actions: read jobs: handle: if: | ( github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'issues' && ( contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude') ) ) runs-on: ubuntu-latest timeout-minutes: 30 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) is_pr=$(jq -r '.isPR // false' 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 "is_pr=$is_pr" >> "$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 if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.command_type == 'review' run: echo 'Review command detected; handled by review workflow.' - 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(', ')}`); } - name: Claude Command Handler if: steps.classify.outputs.should_run == 'true' && steps.classify.outputs.is_maintainer == 'true' && steps.classify.outputs.command_type != 'review' id: claude uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ github.token }} track_progress: false claude_args: >- --model claude-sonnet-4-5 --max-turns 32 --allowed-tools "Read,Edit,Write,Bash(git status),Bash(git add:*),Bash(git commit:*),Bash(git push:*),Bash(git checkout:*),Bash(git switch:*),Bash(git diff:*),Bash(gh pr create:*),Bash(gh pr comment:*),Bash(gh pr view:*),Bash(npm run *),Bash(npm test),Bash(npm run test:*),Bash(npm run build),Bash(npm run lint:*),Bash(node *),Bash(npx *),Bash(rg *),Bash(fd *)" --output-format stream-json prompt: | You are Claude acting via GitHub Actions for the Attio MCP repo. 🚨 **CRITICAL EXECUTION CONSTRAINTS** 🚨 - You have 32 turns MAX before workflow terminates - **BY TURN 20**: STOP using tools and start writing your final response - **BY TURN 25**: You MUST have completed your full markdown report - If you hit turn 32 without outputting a complete response, it will be lost - Your FINAL message must contain the complete analysis/report in markdown format - Do NOT end with tool calls - the last message MUST be pure markdown text ## Context - **Issue**: #${{ steps.classify.outputs.issue }} - **Issue Title**: ${{ steps.context.outputs.issue_title }} - **Triggering Command**: "${{ steps.context.outputs.comment_body }}" **Full Issue Description**: ${{ steps.context.outputs.issue_body }} **Recent Discussion** (last 5 user comments for context): ${{ steps.context.outputs.recent_comments || 'No recent comments' }} **Related Issues** (referenced in issue body): ${{ steps.context.outputs.related_issues || 'No related issues referenced' }} **Previous Claude Analysis** (if this is a follow-up): ${{ steps.context.outputs.previous_analysis || 'No previous analysis - this is the first request on this issue' }} **Follow-Up Instructions**: - If "Previous Claude Analysis" exists, this is a FOLLOW-UP request - Build on your previous analysis, don't repeat it - Reference your previous recommendations when relevant - Acknowledge if user comments since your last analysis provide new context - If user asks a clarifying question, answer directly without repeating full framework - **Note**: Issue body may have been edited since your previous analysis - if you notice discrepancies, acknowledge the updates and adjust recommendations accordingly ## Task Routing First, examine the triggering command to determine your mode: - **ANALYSIS MODE** (vague/exploratory requests like "thoughts?", "help", "analyze this"): Provide comprehensive issue analysis using the framework below - **EXECUTION MODE** (specific instructions like "implement X", "fix Y", "refactor Z"): Follow the maintainer's instructions precisely using repo standards ## Exploration Philosophy: "Good Enough" > "Exhaustive" **CRITICAL MINDSET SHIFT**: Synthesis is more valuable than exhaustive exploration. **Analysis Mode Guidelines:** - Read 1-3 KEY files (issue description, CLAUDE.md, one example file) - You don't need to read the entire codebase - Representative sampling beats exhaustive analysis - After 8-10 tool calls, STOP and synthesize what you've learned - Write sections progressively as you learn (don't wait until the end) **Execution Mode Guidelines:** - Focus on directly relevant files only (not the entire project) - 12-15 tool calls for implementation + testing should be sufficient - If you need more context, make educated assumptions and document them - Leave time for quality gates and final summary **Remember**: A well-reasoned analysis from limited context is better than hitting turn limits with no output. ## ANALYSIS MODE Framework Provide analysis structured in these 5 sections: **1. Issue Quality Assessment** - Problem definition clarity and underlying goals - Requirements completeness and acceptance criteria - Scope boundaries and potential scope creep risks **2. Anti-Pattern Risk Detection** - Infrastructure-without-validation, symptom-driven development, complexity escalation, premature optimization - Explain WHY risky + suggest healthier alternatives **3. Implementation Strategy** - Concrete technical approach (technology/library choices with rationale) - File structure, testing strategy, migration path, integration points **4. Educational Recommendations** - Relevant docs (official docs, CLAUDE.md sections) - Similar solved problems in codebase - Common pitfalls and validation techniques **5. Actionable Next Steps** - Numbered, concrete actions with rationale and dependencies Keep analysis concise but insightful. Use a coaching tone explaining the "why" behind recommendations. ## EXECUTION MODE Guidelines Follow repo standards from `CLAUDE.md`. **Implementation Standards:** - Conventional commit format: `Type: Subject #issue-number` - Single Responsibility Principle (SRP) - Focused, atomic changes **Quality Gates:** - Run appropriate tests: - `npm run test:offline` for unit tests (fast, no API calls) - `npm run test:integration` for API integration tests (requires ATTIO_API_KEY) - `npm run test:e2e` for end-to-end workflows - Run lint/format: `npm run fix:all` - Verify TypeScript: `npx tsc --noEmit` **Workflow:** 1. Read relevant code + CLAUDE.md first 2. Implement changes following standards 3. Run quality gates 4. Commit with conventional format linking issue 5. Report results (branch/PR URL, tests, any issues) **Constraints:** - Never expose secrets or tokens - Ask for clarification if ambiguous - Flag anti-pattern risks proactively ## Reply Format **Analysis Mode**: Use 5-section structure above. Be concise, coaching tone, reference files (file:line), end with actionable steps. **Execution Mode**: Confirm task β†’ implement β†’ run quality gates β†’ report results (branch/PR URL, tests, issues). **Always**: Be concise but thorough, reference code locations, flag anti-pattern risks. --- 🚨 **FINAL REMINDER: OUTPUT YOUR COMPLETE RESPONSE** 🚨 - Your LAST message MUST contain the full markdown report/analysis - Do NOT end with tool calls - the workflow will capture ONLY your final text message - Remember: 32 turn limit, stop tools by turn 20, finish report by turn 25 - If you hit the turn limit without a complete response, the comment will be incomplete/lost - Synthesis > exhaustive exploration - write progressively, don't wait until the end - name: Capture Claude response if: always() && steps.claude.outcome != 'skipped' && steps.claude.outputs.execution_file != '' id: capture uses: actions/github-script@v7 env: EXEC_FILE: ${{ steps.claude.outputs.execution_file }} with: script: | const fs = require('fs'); const { extractAllTextFromSession, dedupeAdjacent } = require('./scripts/workflows/claude/extract-text.js'); const execFile = process.env.EXEC_FILE; if (!execFile || !fs.existsSync(execFile)) { core.info('No execution file found'); return; } const raw = fs.readFileSync(execFile, 'utf8').replace(/^\uFEFF/, ''); const chunks = extractAllTextFromSession(raw); const response = dedupeAdjacent(chunks).join('\n').trim(); if (!response) { core.warning('No response content found'); core.warning(`First 500 chars of execution file:\n${raw.slice(0, 500)}`); core.exportVariable('CLAUDE_RESPONSE_EMPTY', 'true'); return; } core.exportVariable('CLAUDE_RESPONSE', response); core.info(`Captured response (${response.length} chars)`); - name: Post response to issue if: always() && steps.capture.outcome == 'success' && env.CLAUDE_RESPONSE != '' uses: actions/github-script@v7 with: script: | const issueNumber = Number('${{ steps.classify.outputs.issue }}'); const response = process.env.CLAUDE_RESPONSE; if (!issueNumber || !response) { core.info('No issue number or response to post'); return; } const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); async function withRetries(fn, label, attempts = 3) { for (let attempt = 1; attempt <= attempts; attempt++) { try { return await fn(); } catch (error) { if (attempt === attempts) throw error; const delay = 1000 * attempt; core.warning(`Attempt ${attempt} failed. Retrying in ${delay}ms...`); await sleep(delay); } } } const marker = `<!-- claude-command-response -->`; const body = `${marker}\n${response}`; // Post the comment with retries await withRetries( () => github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, body: body }), 'create comment' ); core.info(`Posted response to issue #${issueNumber}`); - name: Post diagnostic comment for empty response if: always() && steps.claude.outcome == 'success' && env.CLAUDE_RESPONSE_EMPTY == 'true' uses: actions/github-script@v7 with: script: | const issueNumber = Number('${{ steps.classify.outputs.issue }}'); if (!issueNumber) return; const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`; const triggerCommand = `${{ steps.context.outputs.comment_body }}`; const lines = [ '<!-- claude-command-diagnostic -->', '⚠️ **Claude Command Handler Issue**', '', 'The workflow completed successfully but no response was captured from Claude. This usually means:', '- Claude used all available turns on tool calls without generating a final text response', '- The output format from claude-code-action was unexpected', '', '**Debug Information:**', `- Workflow Run: ${runUrl}`, `- Issue: #${issueNumber}`, `- Triggering Command: "${triggerCommand}"`, '', 'Please review the workflow logs for details. The response capture logic has been enhanced to handle multiple output formats, but further investigation may be needed.' ]; const body = lines.join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, body: body }); core.info(`Posted diagnostic comment to issue #${issueNumber}`); - 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