From 3356b14c8d089b6f07d0deb2e60e78962e4abf1b Mon Sep 17 00:00:00 2001 From: stranske Date: Sun, 28 Dec 2025 04:14:41 +0000 Subject: [PATCH 1/4] Add bot comment handler workflow New feature to automatically address review comments from bots (Copilot, CodeRabbit, etc.) using the configured AI coding agent. Components: - reusable-bot-comment-handler.yml: Core logic to collect and dispatch - agents-bot-comment-handler.yml: Consumer repo thin caller template - fix_bot_comments.md: Prompt template for agent - bot-comment-handler.md: Documentation Features: - Agent-agnostic: Uses PR label (agent:codex, agent:claude) to select agent - Multiple triggers: Label, Gate completion, manual dispatch - Smart filtering: Skips resolved threads, threads with human replies - Concurrency-safe: Uses existing pr-meta dispatch mechanism Integration: - Runs in parallel with keepalive for agent PRs - Can be used standalone for one-off PRs via label - Added to sync templates for consumer repos --- .../maint-68-sync-consumer-repos.yml | 1 + .../reusable-bot-comment-handler.yml | 379 ++++++++++++++++++ docs/bot-comment-handler.md | 181 +++++++++ .../.github/codex/prompts/fix_bot_comments.md | 47 +++ .../workflows/agents-bot-comment-handler.yml | 163 ++++++++ 5 files changed, 771 insertions(+) create mode 100644 .github/workflows/reusable-bot-comment-handler.yml create mode 100644 docs/bot-comment-handler.md create mode 100644 templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md create mode 100644 templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml diff --git a/.github/workflows/maint-68-sync-consumer-repos.yml b/.github/workflows/maint-68-sync-consumer-repos.yml index 553b2b846..7d9cc86fc 100644 --- a/.github/workflows/maint-68-sync-consumer-repos.yml +++ b/.github/workflows/maint-68-sync-consumer-repos.yml @@ -580,6 +580,7 @@ jobs: "agents-pr-meta.yml" "agents-keepalive-loop.yml" "agents-verifier.yml" + "agents-bot-comment-handler.yml" "autofix.yml" "pr-00-gate.yml" ) diff --git a/.github/workflows/reusable-bot-comment-handler.yml b/.github/workflows/reusable-bot-comment-handler.yml new file mode 100644 index 000000000..67f34ed4e --- /dev/null +++ b/.github/workflows/reusable-bot-comment-handler.yml @@ -0,0 +1,379 @@ +# Reusable workflow to address bot review comments on PRs +# +# Collects unresolved review comments from known bot authors (Copilot, CodeRabbit, etc.) +# and dispatches the configured agent to address them. +# +# Triggers: +# - Called by consumer repo workflows (label trigger or keepalive integration) +# - Manual dispatch for testing +# +# Agent selection: +# - Reads PR labels (agent:codex, agent:claude, etc.) to determine which agent +# - Falls back to Codex if no agent label found +# +# Outputs: +# - comments_found: 'true' if unresolved bot comments were found +# - comments_addressed: Number of comments the agent attempted to address +# - issues_created: 'true' if an issue was created for unaddressable items + +name: Reusable Bot Comment Handler + +on: + workflow_call: + inputs: + pr_number: + description: 'PR number to process' + required: true + type: string + dry_run: + description: 'Preview what would be done without making changes' + required: false + type: boolean + default: false + bot_authors: + description: 'Comma-separated list of bot usernames to process (default: copilot,github-actions)' + required: false + type: string + default: 'copilot[bot],github-actions[bot],coderabbitai[bot]' + skip_if_human_replied: + description: 'Skip comments where a human has already replied' + required: false + type: boolean + default: true + outputs: + comments_found: + description: 'Whether unresolved bot comments were found' + value: ${{ jobs.collect.outputs.comments_found }} + comments_count: + description: 'Number of unresolved bot comments found' + value: ${{ jobs.collect.outputs.comments_count }} + agent_triggered: + description: 'Whether the agent was triggered to address comments' + value: ${{ jobs.dispatch.outputs.triggered }} + secrets: + service_bot_pat: + description: 'PAT for service bot (comments, labels)' + required: false + gh_app_id: + description: 'GitHub App ID for authentication' + required: false + gh_app_private_key: + description: 'GitHub App private key' + required: false + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + # Collect unresolved bot comments + collect: + name: Collect bot comments + runs-on: ubuntu-latest + outputs: + comments_found: ${{ steps.collect.outputs.found }} + comments_count: ${{ steps.collect.outputs.count }} + comments_json: ${{ steps.collect.outputs.comments }} + agent: ${{ steps.agent.outputs.agent }} + agent_workflow: ${{ steps.agent.outputs.workflow }} + steps: + - name: Generate token + id: token + uses: actions/create-github-app-token@v1 + if: inputs.gh_app_id != '' + with: + app-id: ${{ secrets.gh_app_id }} + private-key: ${{ secrets.gh_app_private_key }} + + - name: Resolve token + id: auth + run: | + if [ -n "${{ steps.token.outputs.token }}" ]; then + echo "token=${{ steps.token.outputs.token }}" >> $GITHUB_OUTPUT + elif [ -n "${{ secrets.service_bot_pat }}" ]; then + echo "token=${{ secrets.service_bot_pat }}" >> $GITHUB_OUTPUT + else + echo "token=${{ github.token }}" >> $GITHUB_OUTPUT + fi + + - name: Detect agent from PR labels + id: agent + uses: actions/github-script@v7 + with: + github-token: ${{ steps.auth.outputs.token }} + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: parseInt('${{ inputs.pr_number }}') + }); + + const labels = pr.labels.map(l => l.name); + let agent = 'codex'; // default + let workflow = 'reusable-codex-run.yml'; + + if (labels.includes('agent:claude')) { + agent = 'claude'; + workflow = 'reusable-claude-run.yml'; + } else if (labels.includes('agent:gemini')) { + agent = 'gemini'; + workflow = 'reusable-gemini-run.yml'; + } else if (labels.includes('agent:codex')) { + agent = 'codex'; + workflow = 'reusable-codex-run.yml'; + } + + core.setOutput('agent', agent); + core.setOutput('workflow', workflow); + console.log(`Detected agent: ${agent}, workflow: ${workflow}`); + + - name: Collect unresolved bot comments + id: collect + uses: actions/github-script@v7 + with: + github-token: ${{ steps.auth.outputs.token }} + script: | + const prNumber = parseInt('${{ inputs.pr_number }}'); + const botAuthors = '${{ inputs.bot_authors }}'.split(',').map(s => s.trim()); + const skipIfHumanReplied = ${{ inputs.skip_if_human_replied }}; + + console.log(`Collecting comments for PR #${prNumber}`); + console.log(`Bot authors: ${botAuthors.join(', ')}`); + + // Get all review comments + const { data: comments } = await github.rest.pulls.listReviewComments({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + per_page: 100 + }); + + // Filter for unresolved bot comments + const botComments = []; + const processedThreads = new Set(); + + for (const comment of comments) { + // Skip if not from a known bot + if (!botAuthors.some(bot => comment.user.login.toLowerCase() === bot.toLowerCase())) { + continue; + } + + // Skip resolved threads (check via GraphQL would be better, but this approximates) + // We'll use in_reply_to_id to track threads + const threadId = comment.in_reply_to_id || comment.id; + + if (processedThreads.has(threadId)) { + continue; + } + + // Check if human replied to this thread + if (skipIfHumanReplied) { + const replies = comments.filter(c => + c.in_reply_to_id === comment.id && + !botAuthors.some(bot => c.user.login.toLowerCase() === bot.toLowerCase()) + ); + if (replies.length > 0) { + console.log(`Skipping comment ${comment.id} - human already replied`); + processedThreads.add(threadId); + continue; + } + } + + processedThreads.add(threadId); + + // Extract useful info + botComments.push({ + id: comment.id, + path: comment.path, + line: comment.line || comment.original_line, + body: comment.body, + author: comment.user.login, + url: comment.html_url, + diff_hunk: comment.diff_hunk + }); + } + + const found = botComments.length > 0; + core.setOutput('found', found ? 'true' : 'false'); + core.setOutput('count', botComments.length); + core.setOutput('comments', JSON.stringify(botComments)); + + console.log(`Found ${botComments.length} unresolved bot comments`); + + if (botComments.length > 0) { + console.log('Comments to address:'); + for (const c of botComments) { + console.log(`- ${c.path}:${c.line} (${c.author}): ${c.body.substring(0, 100)}...`); + } + } + + - name: Post summary + if: steps.collect.outputs.found == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ steps.auth.outputs.token }} + script: | + const comments = JSON.parse('${{ steps.collect.outputs.comments }}'.replace(/'/g, "\\'")); + const count = comments.length; + const agent = '${{ steps.agent.outputs.agent }}'; + const dryRun = ${{ inputs.dry_run }}; + + let summary = `## πŸ€– Bot Comment Handler\n\n`; + summary += `Found **${count}** unresolved bot comment(s) to address.\n\n`; + summary += `| File | Line | Bot | Preview |\n`; + summary += `|------|------|-----|--------|\n`; + + for (const c of comments.slice(0, 10)) { + const preview = c.body.substring(0, 50).replace(/\n/g, ' ') + '...'; + summary += `| \`${c.path}\` | ${c.line || 'N/A'} | ${c.author} | ${preview} |\n`; + } + + if (comments.length > 10) { + summary += `\n*...and ${comments.length - 10} more*\n`; + } + + if (dryRun) { + summary += `\n⚠️ **Dry run** - agent will not be triggered.\n`; + } else { + summary += `\nπŸš€ Dispatching **${agent}** to address these comments...\n`; + } + + await core.summary.addRaw(summary).write(); + + # Generate prompt file with bot comments + prepare: + name: Prepare agent prompt + needs: collect + if: needs.collect.outputs.comments_found == 'true' && inputs.dry_run == false + runs-on: ubuntu-latest + outputs: + prompt_ready: ${{ steps.prompt.outputs.ready }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate bot comments prompt + id: prompt + run: | + set -euo pipefail + + mkdir -p .github/codex/prompts + + # Parse comments JSON + COMMENTS='${{ needs.collect.outputs.comments_json }}' + + cat > .github/codex/prompts/fix_bot_comments_dynamic.md << 'PROMPT_HEADER' + # Fix Bot Review Comments + + Review bots have left suggestions on this PR. Address each one: + + ## Instructions + + 1. Read each bot comment below + 2. Implement the suggested fix if it improves the code + 3. If a suggestion is incorrect or doesn't apply, skip it and note why + 4. After fixing, the automation will resolve the comment threads + + ## Bot Comments to Address + + PROMPT_HEADER + + # Append each comment + echo "$COMMENTS" | jq -r '.[] | "### \(.path):\(.line // "N/A")\n\n**From:** \(.author)\n\n```\n\(.body)\n```\n\n**Context (diff hunk):**\n```diff\n\(.diff_hunk)\n```\n\n---\n"' >> .github/codex/prompts/fix_bot_comments_dynamic.md + + cat >> .github/codex/prompts/fix_bot_comments_dynamic.md << 'PROMPT_FOOTER' + + ## After Addressing Comments + + - Commit your changes with message: "fix: address bot review comments" + - The automation will resolve comment threads for fixes you made + - For suggestions you skipped, the automation will create a follow-up issue + PROMPT_FOOTER + + echo "ready=true" >> $GITHUB_OUTPUT + echo "Generated prompt with $(echo "$COMMENTS" | jq length) comments" + + - name: Upload prompt artifact + uses: actions/upload-artifact@v4 + with: + name: bot-comments-prompt-${{ github.run_id }} + path: .github/codex/prompts/fix_bot_comments_dynamic.md + retention-days: 1 + + # Dispatch to agent + dispatch: + name: Dispatch to agent + needs: [collect, prepare] + if: needs.collect.outputs.comments_found == 'true' && inputs.dry_run == false + runs-on: ubuntu-latest + outputs: + triggered: ${{ steps.dispatch.outputs.triggered }} + steps: + - name: Generate token + id: token + uses: actions/create-github-app-token@v1 + if: inputs.gh_app_id != '' + continue-on-error: true + with: + app-id: ${{ secrets.gh_app_id }} + private-key: ${{ secrets.gh_app_private_key }} + + - name: Resolve token + id: auth + run: | + if [ -n "${{ steps.token.outputs.token }}" ]; then + echo "token=${{ steps.token.outputs.token }}" >> $GITHUB_OUTPUT + elif [ -n "${{ secrets.service_bot_pat }}" ]; then + echo "token=${{ secrets.service_bot_pat }}" >> $GITHUB_OUTPUT + else + echo "token=${{ github.token }}" >> $GITHUB_OUTPUT + fi + + - name: Post agent command comment + id: dispatch + uses: actions/github-script@v7 + with: + github-token: ${{ steps.auth.outputs.token }} + script: | + const prNumber = parseInt('${{ inputs.pr_number }}'); + const agent = '${{ needs.collect.outputs.agent }}'; + const count = '${{ needs.collect.outputs.comments_count }}'; + + // Post a comment that triggers the agent via pr-meta workflow + // This uses the existing concurrency/dispatch infrastructure + const comment = `@${agent} Please address the ${count} bot review comment(s) on this PR. + + Focus on: + 1. Implementing suggested fixes that improve the code + 2. Skipping suggestions that don't apply (note why in your response) + + The bot comment handler workflow has prepared context for you.`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: comment + }); + + core.setOutput('triggered', 'true'); + console.log(`Posted @${agent} command on PR #${prNumber}`); + + # Summary + summary: + name: Generate summary + needs: [collect, dispatch] + if: always() + runs-on: ubuntu-latest + steps: + - name: Final summary + run: | + echo "## Bot Comment Handler Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Comments Found | ${{ needs.collect.outputs.comments_count || 0 }} |" >> $GITHUB_STEP_SUMMARY + echo "| Agent | ${{ needs.collect.outputs.agent || 'N/A' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Agent Triggered | ${{ needs.dispatch.outputs.triggered || 'false' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dry Run | ${{ inputs.dry_run }} |" >> $GITHUB_STEP_SUMMARY diff --git a/docs/bot-comment-handler.md b/docs/bot-comment-handler.md new file mode 100644 index 000000000..a371510a0 --- /dev/null +++ b/docs/bot-comment-handler.md @@ -0,0 +1,181 @@ +# Bot Comment Handler + +Automatically addresses review comments from bots (Copilot, CodeRabbit, etc.) using the configured AI coding agent. + +## Overview + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ reusable-bot-comment-handler.yml (Workflows repo) β”‚ +β”‚ - Collects unresolved bot comments via GitHub API β”‚ +β”‚ - Detects agent from PR labels (agent:codex, agent:claude) β”‚ +β”‚ - Posts @agent command to trigger fix β”‚ +β”‚ - Creates issue for unaddressable items β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό + Label trigger Gate completion Manual dispatch + (one-off PRs) (agent PRs) (testing) +``` + +## Triggers + +| Trigger | When | Use Case | +|---------|------|----------| +| `autofix:bot-comments` label | Manual | One-off PRs, ad-hoc fixes | +| Gate workflow completion | Automatic | Agent PRs (has `agent:*` label) | +| `workflow_dispatch` | Manual | Testing, debugging | + +## Agent Selection + +The workflow reads the PR's labels to determine which agent to use: + +| Label | Agent | Workflow | +|-------|-------|----------| +| `agent:codex` | Codex CLI | `reusable-codex-run.yml` | +| `agent:claude` | Claude | `reusable-claude-run.yml` | +| `agent:gemini` | Gemini | `reusable-gemini-run.yml` | +| (none) | Codex (default) | `reusable-codex-run.yml` | + +**To switch agents:** Change the PR label. No workflow changes needed. + +## Bot Authors + +By default, the workflow processes comments from: +- `copilot[bot]` - GitHub Copilot code review +- `github-actions[bot]` - GitHub Actions (lint, type check suggestions) +- `coderabbitai[bot]` - CodeRabbit AI review + +Configure via the `bot_authors` input. + +## Behavior + +### What Gets Processed + +- βœ… Unresolved review comments from known bots +- βœ… Inline code suggestions +- ❌ Comments where a human has already replied (skipped by default) +- ❌ General PR comments (not inline reviews) +- ❌ Resolved threads + +### Agent Instructions + +The agent is instructed to: +1. **Fix** suggestions that improve the code +2. **Skip** suggestions that don't apply or are incorrect +3. **Document** decisions in the commit message + +### After Processing + +- Comment threads with fixes are resolved automatically +- Skipped/complex items can be turned into follow-up issues +- Summary posted to workflow run + +## Consumer Repo Setup + +### 1. Add the workflow + +Copy `agents-bot-comment-handler.yml` to `.github/workflows/`: + +```bash +curl -sL https://raw.githubusercontent.com/stranske/Workflows/main/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml \ + -o .github/workflows/agents-bot-comment-handler.yml +``` + +### 2. Add the prompt template + +```bash +mkdir -p .github/codex/prompts +curl -sL https://raw.githubusercontent.com/stranske/Workflows/main/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md \ + -o .github/codex/prompts/fix_bot_comments.md +``` + +### 3. Create the label + +Create `autofix:bot-comments` label in your repository: +- **Name:** `autofix:bot-comments` +- **Color:** `#7057ff` (purple) +- **Description:** Trigger bot to address review bot comments + +### 4. Ensure secrets + +The workflow uses the same secrets as other agent workflows: +- `SERVICE_BOT_PAT` or GitHub App credentials +- Same permissions as keepalive + +## Usage + +### Manual (Label Trigger) + +1. Open a PR with bot review comments +2. Add the `autofix:bot-comments` label +3. Workflow collects comments and dispatches agent +4. Label is automatically removed after processing + +### Automatic (Agent PRs) + +For PRs with `agent:codex` or other agent labels: +1. Gate workflow completes successfully +2. Bot comment handler checks for unresolved comments +3. If found, dispatches agent to address them +4. Agent fixes flow into normal keepalive cycle + +### Manual Dispatch + +```bash +gh workflow run agents-bot-comment-handler.yml \ + -f pr_number=123 \ + -f dry_run=true +``` + +## Integration with Keepalive + +The bot comment handler runs **in parallel** with the normal keepalive cycle: + +``` +Push β†’ Gate runs β†’ Bot comment handler checks for comments + β†’ Keepalive evaluates tasks + +Both can trigger agent, but concurrency group ensures orderly execution +``` + +The agent command posted by bot comment handler goes through `agents-pr-meta.yml`, which uses the same concurrency group as keepalive, preventing race conditions. + +## Troubleshooting + +### No comments found + +- Check that bot authors match (case-sensitive) +- Verify comments are review comments (not issue comments) +- Check if human already replied (skipped by default) + +### Agent not triggered + +- Verify PR has an agent label or workflow is using correct default +- Check secrets are configured +- Review workflow run logs + +### Agent doesn't address all comments + +- Some suggestions may not have enough context +- Agent may skip suggestions it deems incorrect +- Check commit message for agent's reasoning + +## Inputs Reference + +| Input | Type | Default | Description | +|-------|------|---------|-------------| +| `pr_number` | string | required | PR number to process | +| `dry_run` | boolean | false | Preview without changes | +| `bot_authors` | string | `copilot[bot],github-actions[bot],coderabbitai[bot]` | Bot usernames to process | +| `skip_if_human_replied` | boolean | true | Skip threads with human replies | + +## Outputs Reference + +| Output | Description | +|--------|-------------| +| `comments_found` | Whether unresolved bot comments were found | +| `comments_count` | Number of comments found | +| `agent_triggered` | Whether agent was dispatched | diff --git a/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md b/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md new file mode 100644 index 000000000..9fe21030f --- /dev/null +++ b/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md @@ -0,0 +1,47 @@ +# Fix Bot Review Comments + +Review bots have left suggestions on this PR. Your task is to address each one. + +## Instructions + +1. **Read each bot comment** in the sections below +2. **Implement suggested fixes** that improve code quality, correctness, or maintainability +3. **Skip suggestions** that are incorrect, don't apply, or would make the code worse +4. **Document your decisions** in your commit message + +## Guidelines + +### When to FIX a suggestion: +- It corrects a genuine bug or issue +- It improves type safety or error handling +- It follows established project patterns +- It improves readability or maintainability + +### When to SKIP a suggestion: +- It contradicts project conventions +- It's based on incomplete context +- The suggested change would break functionality +- It's purely stylistic with no clear benefit + +## After Addressing Comments + +1. **Commit** with message format: `fix: address bot review comments` +2. **Include** in your commit message which suggestions you addressed and skipped: + ``` + fix: address bot review comments + + Addressed: + - Fixed type annotation in auth.py + - Added null check in handler.py + + Skipped: + - Suggestion to rename variable (matches project convention) + ``` + +3. The automation will: + - Resolve comment threads for fixes you made + - Create a follow-up issue for complex items that need human review + +## Bot Comments + + diff --git a/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml b/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml new file mode 100644 index 000000000..db2d95b7e --- /dev/null +++ b/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml @@ -0,0 +1,163 @@ +# Bot Comment Handler - Thin caller for consumer repos +# +# Addresses unresolved review comments from bots (Copilot, CodeRabbit, etc.) +# by dispatching the configured agent to fix them. +# +# Triggers: +# - PR labeled with 'autofix:bot-comments' (manual trigger) +# - Gate workflow completion (automatic for agent PRs) +# - Manual dispatch for testing +# +# Agent selection: +# - Uses PR's agent:* label (agent:codex, agent:claude, etc.) +# - Falls back to Codex if no agent label +# +# Copy this file to: .github/workflows/agents-bot-comment-handler.yml + +name: Agents Bot Comment Handler + +on: + # Manual trigger via label + pull_request: + types: [labeled] + + # Automatic trigger after Gate completes (for agent PRs) + workflow_run: + workflows: ["Gate"] + types: [completed] + branches-ignore: [main] + + # Manual dispatch for testing + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to process' + required: true + type: string + dry_run: + description: 'Preview without making changes' + required: false + type: boolean + default: false + +permissions: + contents: read + pull-requests: write + issues: write + actions: read + +concurrency: + group: bot-comments-${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} + cancel-in-progress: false + +jobs: + # Resolve PR number from different trigger types + resolve: + name: Resolve PR + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.resolve.outputs.pr_number }} + should_run: ${{ steps.resolve.outputs.should_run }} + steps: + - name: Resolve PR number and check conditions + id: resolve + uses: actions/github-script@v7 + with: + script: | + const eventName = context.eventName; + let prNumber = null; + let shouldRun = false; + + if (eventName === 'workflow_dispatch') { + prNumber = '${{ inputs.pr_number }}'; + shouldRun = true; + console.log(`Manual dispatch for PR #${prNumber}`); + } + else if (eventName === 'pull_request') { + // Only run if labeled with autofix:bot-comments + const label = context.payload.label?.name; + if (label === 'autofix:bot-comments') { + prNumber = context.payload.pull_request.number; + shouldRun = true; + console.log(`Label trigger for PR #${prNumber}`); + } else { + console.log(`Ignoring label: ${label}`); + } + } + else if (eventName === 'workflow_run') { + // Only run if Gate succeeded and PR has agent:* label + const workflowRun = context.payload.workflow_run; + + if (workflowRun.conclusion !== 'success') { + console.log(`Gate did not succeed (${workflowRun.conclusion}), skipping`); + core.setOutput('should_run', 'false'); + return; + } + + // Get PR from workflow run + const prs = workflowRun.pull_requests; + if (!prs || prs.length === 0) { + console.log('No PR associated with workflow run'); + core.setOutput('should_run', 'false'); + return; + } + + prNumber = prs[0].number; + + // Check if PR has agent label + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + const hasAgentLabel = pr.labels.some(l => /^agent:/.test(l.name)); + if (!hasAgentLabel) { + console.log(`PR #${prNumber} has no agent label, skipping`); + core.setOutput('should_run', 'false'); + return; + } + + shouldRun = true; + console.log(`Gate completion trigger for agent PR #${prNumber}`); + } + + core.setOutput('pr_number', prNumber || ''); + core.setOutput('should_run', shouldRun ? 'true' : 'false'); + + # Call the reusable workflow + handle: + name: Handle bot comments + needs: resolve + if: needs.resolve.outputs.should_run == 'true' + uses: stranske/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main + with: + pr_number: ${{ needs.resolve.outputs.pr_number }} + dry_run: ${{ inputs.dry_run || false }} + secrets: + service_bot_pat: ${{ secrets.SERVICE_BOT_PAT }} + gh_app_id: ${{ secrets.GH_APP_ID }} + gh_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} + + # Remove the trigger label after processing + cleanup: + name: Cleanup + needs: [resolve, handle] + if: always() && github.event_name == 'pull_request' && github.event.label.name == 'autofix:bot-comments' + runs-on: ubuntu-latest + steps: + - name: Remove trigger label + uses: actions/github-script@v7 + with: + script: | + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + name: 'autofix:bot-comments' + }); + console.log('Removed autofix:bot-comments label'); + } catch (e) { + console.log('Label already removed or does not exist'); + } From 198c6037d5e64c1d46743dba941558e0390d6bde Mon Sep 17 00:00:00 2001 From: stranske Date: Sun, 28 Dec 2025 04:31:39 +0000 Subject: [PATCH 2/4] fix: address bot review comments on PR #246 Addressed: - JSON escaping: Use env vars + process.env instead of inline template injection - secrets vs inputs: Changed 'if: inputs.gh_app_id' to 'if: secrets.gh_app_id' - branches-ignore: Removed unsupported filter, added branch check in job logic - concurrency fallback: Added github.run_id fallback for empty PR numbers - shellcheck: Quoted variables, used env vars instead of inline substitution - API error handling: Added try-catch around PR fetch calls - Thread detection: Fixed to check entire thread for human replies - Docs: Removed claims about auto-resolution and issue creation (not implemented) - Output docs: Fixed comment to match actual outputs Skipped: - None - all suggestions were valid --- .../reusable-bot-comment-handler.yml | 112 +++++++++++------- docs/bot-comment-handler.md | 105 +++++++--------- .../.github/codex/prompts/fix_bot_comments.md | 4 - .../workflows/agents-bot-comment-handler.yml | 37 ++++-- 4 files changed, 139 insertions(+), 119 deletions(-) diff --git a/.github/workflows/reusable-bot-comment-handler.yml b/.github/workflows/reusable-bot-comment-handler.yml index 67f34ed4e..dcccdecd5 100644 --- a/.github/workflows/reusable-bot-comment-handler.yml +++ b/.github/workflows/reusable-bot-comment-handler.yml @@ -13,8 +13,8 @@ # # Outputs: # - comments_found: 'true' if unresolved bot comments were found -# - comments_addressed: Number of comments the agent attempted to address -# - issues_created: 'true' if an issue was created for unaddressable items +# - comments_count: Number of unresolved bot comments found +# - agent_triggered: 'true' if the agent was triggered to address comments name: Reusable Bot Comment Handler @@ -31,7 +31,7 @@ on: type: boolean default: false bot_authors: - description: 'Comma-separated list of bot usernames to process (default: copilot,github-actions)' + description: 'Comma-separated list of bot login names including [bot] suffix (default: copilot[bot],github-actions[bot],coderabbitai[bot])' required: false type: string default: 'copilot[bot],github-actions[bot],coderabbitai[bot]' @@ -81,20 +81,25 @@ jobs: - name: Generate token id: token uses: actions/create-github-app-token@v1 - if: inputs.gh_app_id != '' + if: ${{ secrets.gh_app_id != '' }} + continue-on-error: true with: app-id: ${{ secrets.gh_app_id }} private-key: ${{ secrets.gh_app_private_key }} - name: Resolve token id: auth + env: + TOKEN_OUTPUT: ${{ steps.token.outputs.token }} + SERVICE_PAT: ${{ secrets.service_bot_pat }} + GITHUB_TOKEN: ${{ github.token }} run: | - if [ -n "${{ steps.token.outputs.token }}" ]; then - echo "token=${{ steps.token.outputs.token }}" >> $GITHUB_OUTPUT - elif [ -n "${{ secrets.service_bot_pat }}" ]; then - echo "token=${{ secrets.service_bot_pat }}" >> $GITHUB_OUTPUT + if [ -n "${TOKEN_OUTPUT}" ]; then + echo "token=${TOKEN_OUTPUT}" >> "$GITHUB_OUTPUT" + elif [ -n "${SERVICE_PAT}" ]; then + echo "token=${SERVICE_PAT}" >> "$GITHUB_OUTPUT" else - echo "token=${{ github.token }}" >> $GITHUB_OUTPUT + echo "token=${GITHUB_TOKEN}" >> "$GITHUB_OUTPUT" fi - name: Detect agent from PR labels @@ -103,11 +108,21 @@ jobs: with: github-token: ${{ steps.auth.outputs.token }} script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: parseInt('${{ inputs.pr_number }}') - }); + const prNumber = parseInt('${{ inputs.pr_number }}'); + + let pr; + try { + const response = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + pr = response.data; + } catch (error) { + console.log(`Failed to fetch PR #${prNumber}: ${error.message}`); + core.setFailed(`Could not fetch PR #${prNumber}`); + return; + } const labels = pr.labels.map(l => l.name); let agent = 'codex'; // default @@ -167,13 +182,13 @@ jobs: continue; } - // Check if human replied to this thread + // Check if human replied to this thread (check all comments in thread) if (skipIfHumanReplied) { - const replies = comments.filter(c => - c.in_reply_to_id === comment.id && + const threadReplies = comments.filter(c => + (c.in_reply_to_id === comment.id || c.in_reply_to_id === threadId) && !botAuthors.some(bot => c.user.login.toLowerCase() === bot.toLowerCase()) ); - if (replies.length > 0) { + if (threadReplies.length > 0) { console.log(`Skipping comment ${comment.id} - human already replied`); processedThreads.add(threadId); continue; @@ -211,10 +226,12 @@ jobs: - name: Post summary if: steps.collect.outputs.found == 'true' uses: actions/github-script@v7 + env: + COMMENTS_JSON: ${{ steps.collect.outputs.comments }} with: github-token: ${{ steps.auth.outputs.token }} script: | - const comments = JSON.parse('${{ steps.collect.outputs.comments }}'.replace(/'/g, "\\'")); + const comments = JSON.parse(process.env.COMMENTS_JSON); const count = comments.length; const agent = '${{ steps.agent.outputs.agent }}'; const dryRun = ${{ inputs.dry_run }}; @@ -255,14 +272,13 @@ jobs: - name: Generate bot comments prompt id: prompt + env: + COMMENTS_JSON: ${{ needs.collect.outputs.comments_json }} run: | set -euo pipefail mkdir -p .github/codex/prompts - # Parse comments JSON - COMMENTS='${{ needs.collect.outputs.comments_json }}' - cat > .github/codex/prompts/fix_bot_comments_dynamic.md << 'PROMPT_HEADER' # Fix Bot Review Comments @@ -273,26 +289,25 @@ jobs: 1. Read each bot comment below 2. Implement the suggested fix if it improves the code 3. If a suggestion is incorrect or doesn't apply, skip it and note why - 4. After fixing, the automation will resolve the comment threads + 4. After fixing, summarize what you addressed in your commit message ## Bot Comments to Address PROMPT_HEADER - # Append each comment - echo "$COMMENTS" | jq -r '.[] | "### \(.path):\(.line // "N/A")\n\n**From:** \(.author)\n\n```\n\(.body)\n```\n\n**Context (diff hunk):**\n```diff\n\(.diff_hunk)\n```\n\n---\n"' >> .github/codex/prompts/fix_bot_comments_dynamic.md + # Append each comment using jq with proper escaping + echo "${COMMENTS_JSON}" | jq -r '.[] | "### \(.path):\(.line // "N/A")\n\n**From:** \(.author)\n\n```\n\(.body)\n```\n\n**Context (diff hunk):**\n```diff\n\(.diff_hunk)\n```\n\n---\n"' >> .github/codex/prompts/fix_bot_comments_dynamic.md cat >> .github/codex/prompts/fix_bot_comments_dynamic.md << 'PROMPT_FOOTER' ## After Addressing Comments - Commit your changes with message: "fix: address bot review comments" - - The automation will resolve comment threads for fixes you made - - For suggestions you skipped, the automation will create a follow-up issue + - Include which suggestions you addressed vs skipped in the commit message PROMPT_FOOTER - echo "ready=true" >> $GITHUB_OUTPUT - echo "Generated prompt with $(echo "$COMMENTS" | jq length) comments" + echo "ready=true" >> "$GITHUB_OUTPUT" + echo "Generated prompt with $(echo "${COMMENTS_JSON}" | jq length) comments" - name: Upload prompt artifact uses: actions/upload-artifact@v4 @@ -313,7 +328,7 @@ jobs: - name: Generate token id: token uses: actions/create-github-app-token@v1 - if: inputs.gh_app_id != '' + if: ${{ secrets.gh_app_id != '' }} continue-on-error: true with: app-id: ${{ secrets.gh_app_id }} @@ -321,13 +336,17 @@ jobs: - name: Resolve token id: auth + env: + TOKEN_OUTPUT: ${{ steps.token.outputs.token }} + SERVICE_PAT: ${{ secrets.service_bot_pat }} + GITHUB_TOKEN: ${{ github.token }} run: | - if [ -n "${{ steps.token.outputs.token }}" ]; then - echo "token=${{ steps.token.outputs.token }}" >> $GITHUB_OUTPUT - elif [ -n "${{ secrets.service_bot_pat }}" ]; then - echo "token=${{ secrets.service_bot_pat }}" >> $GITHUB_OUTPUT + if [ -n "${TOKEN_OUTPUT}" ]; then + echo "token=${TOKEN_OUTPUT}" >> "$GITHUB_OUTPUT" + elif [ -n "${SERVICE_PAT}" ]; then + echo "token=${SERVICE_PAT}" >> "$GITHUB_OUTPUT" else - echo "token=${{ github.token }}" >> $GITHUB_OUTPUT + echo "token=${GITHUB_TOKEN}" >> "$GITHUB_OUTPUT" fi - name: Post agent command comment @@ -368,12 +387,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Final summary + env: + COMMENTS_COUNT: ${{ needs.collect.outputs.comments_count }} + AGENT: ${{ needs.collect.outputs.agent }} + TRIGGERED: ${{ needs.dispatch.outputs.triggered }} + DRY_RUN: ${{ inputs.dry_run }} run: | - echo "## Bot Comment Handler Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Comments Found | ${{ needs.collect.outputs.comments_count || 0 }} |" >> $GITHUB_STEP_SUMMARY - echo "| Agent | ${{ needs.collect.outputs.agent || 'N/A' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Agent Triggered | ${{ needs.dispatch.outputs.triggered || 'false' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Dry Run | ${{ inputs.dry_run }} |" >> $GITHUB_STEP_SUMMARY + { + echo "## Bot Comment Handler Results" + echo "" + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| Comments Found | ${COMMENTS_COUNT:-0} |" + echo "| Agent | ${AGENT:-N/A} |" + echo "| Agent Triggered | ${TRIGGERED:-false} |" + echo "| Dry Run | ${DRY_RUN} |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/bot-comment-handler.md b/docs/bot-comment-handler.md index a371510a0..6f5acccf5 100644 --- a/docs/bot-comment-handler.md +++ b/docs/bot-comment-handler.md @@ -10,7 +10,6 @@ Automatically addresses review comments from bots (Copilot, CodeRabbit, etc.) us β”‚ - Collects unresolved bot comments via GitHub API β”‚ β”‚ - Detects agent from PR labels (agent:codex, agent:claude) β”‚ β”‚ - Posts @agent command to trigger fix β”‚ -β”‚ - Creates issue for unaddressable items β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” @@ -69,9 +68,9 @@ The agent is instructed to: ### After Processing -- Comment threads with fixes are resolved automatically -- Skipped/complex items can be turned into follow-up issues -- Summary posted to workflow run +- Agent commits fixes with message documenting what was addressed vs skipped +- Summary posted to workflow run showing all comments found +- Skipped/complex items are highlighted in the summary for potential follow-up ## Consumer Repo Setup @@ -99,83 +98,67 @@ Create `autofix:bot-comments` label in your repository: - **Color:** `#7057ff` (purple) - **Description:** Trigger bot to address review bot comments -### 4. Ensure secrets - -The workflow uses the same secrets as other agent workflows: -- `SERVICE_BOT_PAT` or GitHub App credentials -- Same permissions as keepalive - ## Usage -### Manual (Label Trigger) +### One-off PRs -1. Open a PR with bot review comments -2. Add the `autofix:bot-comments` label -3. Workflow collects comments and dispatches agent -4. Label is automatically removed after processing +Add the `autofix:bot-comments` label to any PR with bot review comments. The workflow will: +1. Collect all unresolved bot comments +2. Post `@` command to trigger the agent +3. Remove the label after processing -### Automatic (Agent PRs) +### Agent PRs (Automatic) -For PRs with `agent:codex` or other agent labels: -1. Gate workflow completes successfully -2. Bot comment handler checks for unresolved comments -3. If found, dispatches agent to address them -4. Agent fixes flow into normal keepalive cycle +For PRs created by agents (with `agent:*` labels), the workflow automatically runs after Gate completes: +1. Checks if Gate succeeded +2. Collects any bot review comments +3. Dispatches the agent to address them -### Manual Dispatch +### Testing ```bash -gh workflow run agents-bot-comment-handler.yml \ - -f pr_number=123 \ - -f dry_run=true +# Dry run - see what would be processed +gh workflow run agents-bot-comment-handler.yml -f pr_number=123 -f dry_run=true + +# Full run +gh workflow run agents-bot-comment-handler.yml -f pr_number=123 ``` -## Integration with Keepalive +## Configuration -The bot comment handler runs **in parallel** with the normal keepalive cycle: +### Inputs -``` -Push β†’ Gate runs β†’ Bot comment handler checks for comments - β†’ Keepalive evaluates tasks - -Both can trigger agent, but concurrency group ensures orderly execution -``` +| Input | Default | Description | +|-------|---------|-------------| +| `pr_number` | (required) | PR number to process | +| `dry_run` | `false` | Preview without triggering agent | +| `bot_authors` | `copilot[bot],github-actions[bot],coderabbitai[bot]` | Bot login names to process | +| `skip_if_human_replied` | `true` | Skip threads with human replies | -The agent command posted by bot comment handler goes through `agents-pr-meta.yml`, which uses the same concurrency group as keepalive, preventing race conditions. +### Secrets + +| Secret | Required | Description | +|--------|----------|-------------| +| `SERVICE_BOT_PAT` | No | PAT for service bot account | +| `GH_APP_ID` | No | GitHub App ID (alternative auth) | +| `GH_APP_PRIVATE_KEY` | No | GitHub App private key | ## Troubleshooting ### No comments found -- Check that bot authors match (case-sensitive) -- Verify comments are review comments (not issue comments) -- Check if human already replied (skipped by default) +- Check that bot authors match exactly (including `[bot]` suffix) +- Verify comments are review comments, not PR comments +- Check if threads were already resolved ### Agent not triggered -- Verify PR has an agent label or workflow is using correct default -- Check secrets are configured -- Review workflow run logs - -### Agent doesn't address all comments - -- Some suggestions may not have enough context -- Agent may skip suggestions it deems incorrect -- Check commit message for agent's reasoning - -## Inputs Reference - -| Input | Type | Default | Description | -|-------|------|---------|-------------| -| `pr_number` | string | required | PR number to process | -| `dry_run` | boolean | false | Preview without changes | -| `bot_authors` | string | `copilot[bot],github-actions[bot],coderabbitai[bot]` | Bot usernames to process | -| `skip_if_human_replied` | boolean | true | Skip threads with human replies | +- Ensure `dry_run` is not enabled +- Check workflow permissions (needs `pull-requests: write`) +- Verify authentication tokens are configured -## Outputs Reference +### Gate trigger not working -| Output | Description | -|--------|-------------| -| `comments_found` | Whether unresolved bot comments were found | -| `comments_count` | Number of comments found | -| `agent_triggered` | Whether agent was dispatched | +- Ensure PR has an `agent:*` label +- Check that Gate workflow completed successfully +- Verify workflow_run trigger is configured correctly diff --git a/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md b/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md index 9fe21030f..bb2945e50 100644 --- a/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md +++ b/templates/consumer-repo/.github/codex/prompts/fix_bot_comments.md @@ -38,10 +38,6 @@ Review bots have left suggestions on this PR. Your task is to address each one. - Suggestion to rename variable (matches project convention) ``` -3. The automation will: - - Resolve comment threads for fixes you made - - Create a follow-up issue for complex items that need human review - ## Bot Comments diff --git a/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml b/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml index db2d95b7e..cc451426b 100644 --- a/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml +++ b/templates/consumer-repo/.github/workflows/agents-bot-comment-handler.yml @@ -12,7 +12,7 @@ # - Uses PR's agent:* label (agent:codex, agent:claude, etc.) # - Falls back to Codex if no agent label # -# Copy this file to: .github/workflows/agents-bot-comment-handler.yml +# Workflow file: .github/workflows/agents-bot-comment-handler.yml name: Agents Bot Comment Handler @@ -22,10 +22,10 @@ on: types: [labeled] # Automatic trigger after Gate completes (for agent PRs) + # Note: branches-ignore is not supported for workflow_run, filtering is done in job logic workflow_run: workflows: ["Gate"] types: [completed] - branches-ignore: [main] # Manual dispatch for testing workflow_dispatch: @@ -47,7 +47,7 @@ permissions: actions: read concurrency: - group: bot-comments-${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} + group: bot-comments-${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || inputs.pr_number || github.run_id }} cancel-in-progress: false jobs: @@ -88,6 +88,13 @@ jobs: // Only run if Gate succeeded and PR has agent:* label const workflowRun = context.payload.workflow_run; + // Skip main branch + if (workflowRun.head_branch === 'main') { + console.log('Skipping main branch'); + core.setOutput('should_run', 'false'); + return; + } + if (workflowRun.conclusion !== 'success') { console.log(`Gate did not succeed (${workflowRun.conclusion}), skipping`); core.setOutput('should_run', 'false'); @@ -105,11 +112,19 @@ jobs: prNumber = prs[0].number; // Check if PR has agent label - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber - }); + let pr; + try { + const response = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + pr = response.data; + } catch (error) { + console.log(`Failed to fetch PR #${prNumber} details, skipping. Error: ${error.message || error}`); + core.setOutput('should_run', 'false'); + return; + } const hasAgentLabel = pr.labels.some(l => /^agent:/.test(l.name)); if (!hasAgentLabel) { @@ -133,7 +148,7 @@ jobs: uses: stranske/Workflows/.github/workflows/reusable-bot-comment-handler.yml@main with: pr_number: ${{ needs.resolve.outputs.pr_number }} - dry_run: ${{ inputs.dry_run || false }} + dry_run: ${{ inputs.dry_run == true }} secrets: service_bot_pat: ${{ secrets.SERVICE_BOT_PAT }} gh_app_id: ${{ secrets.GH_APP_ID }} @@ -158,6 +173,6 @@ jobs: name: 'autofix:bot-comments' }); console.log('Removed autofix:bot-comments label'); - } catch (e) { - console.log('Label already removed or does not exist'); + } catch (error) { + console.log(`Could not remove label: ${error.message}`); } From bb966654060c656cea651d318b1d4a82e95f5b58 Mon Sep 17 00:00:00 2001 From: stranske Date: Sun, 28 Dec 2025 04:33:41 +0000 Subject: [PATCH 3/4] fix: remove secrets context from step-level if conditions GitHub Actions doesn't allow 'secrets' context in step-level if conditions. The continue-on-error: true handles the case when App credentials aren't provided. --- .github/workflows/reusable-bot-comment-handler.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-bot-comment-handler.yml b/.github/workflows/reusable-bot-comment-handler.yml index dcccdecd5..11b98c23b 100644 --- a/.github/workflows/reusable-bot-comment-handler.yml +++ b/.github/workflows/reusable-bot-comment-handler.yml @@ -78,10 +78,10 @@ jobs: agent: ${{ steps.agent.outputs.agent }} agent_workflow: ${{ steps.agent.outputs.workflow }} steps: - - name: Generate token + - name: Generate token (if App configured) id: token uses: actions/create-github-app-token@v1 - if: ${{ secrets.gh_app_id != '' }} + continue-on-error: true with: app-id: ${{ secrets.gh_app_id }} @@ -325,10 +325,9 @@ jobs: outputs: triggered: ${{ steps.dispatch.outputs.triggered }} steps: - - name: Generate token + - name: Generate token (if App configured) id: token uses: actions/create-github-app-token@v1 - if: ${{ secrets.gh_app_id != '' }} continue-on-error: true with: app-id: ${{ secrets.gh_app_id }} From c42602e94f3f365f7a9c4cd75b660578007556f8 Mon Sep 17 00:00:00 2001 From: stranske Date: Sun, 28 Dec 2025 04:36:26 +0000 Subject: [PATCH 4/4] docs: add reusable-bot-comment-handler to workflow inventory - Add to docs/ci/WORKFLOWS.md reusable workflows table - Add to docs/ci/WORKFLOW_SYSTEM.md primary workflows list - Add expected name mapping to test_workflow_naming.py --- docs/ci/WORKFLOWS.md | 1 + docs/ci/WORKFLOW_SYSTEM.md | 2 +- tests/workflows/test_workflow_naming.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ci/WORKFLOWS.md b/docs/ci/WORKFLOWS.md index 5747799da..99e990bd8 100644 --- a/docs/ci/WORKFLOWS.md +++ b/docs/ci/WORKFLOWS.md @@ -78,6 +78,7 @@ pull_request ──▢ Gate ──▢ Summary comment & status | [`reusable-70-orchestrator-main.yml`](../../.github/workflows/reusable-70-orchestrator-main.yml) | None. | Consumes init outputs; reports via summaries/artifacts. | | [`reusable-agents-issue-bridge.yml`](../../.github/workflows/reusable-agents-issue-bridge.yml) | None. | Bridge emits PRs/comments only. | | [`reusable-agents-verifier.yml`](../../.github/workflows/reusable-agents-verifier.yml) | None. | Post-merge verification with CI wait logic; creates follow-up issues in consumer repos. | +| [`reusable-bot-comment-handler.yml`](../../.github/workflows/reusable-bot-comment-handler.yml) | `comments_found`, `comments_count`, `agent_triggered`. | Collects and dispatches agent to address bot review comments. | ## Pull Request Gate diff --git a/docs/ci/WORKFLOW_SYSTEM.md b/docs/ci/WORKFLOW_SYSTEM.md index 60f6b00e4..62cb93c45 100644 --- a/docs/ci/WORKFLOW_SYSTEM.md +++ b/docs/ci/WORKFLOW_SYSTEM.md @@ -386,7 +386,7 @@ fires where” without diving into the full tables: [workflow history](https://github.com/stranske/Trend_Model_Project/actions/workflows/agents-guard.yml). - **Error checking, linting, and testing topology** - **Primary workflows.** `reusable-10-ci-python.yml`, `reusable-12-ci-docker.yml`, - `reusable-16-agents.yml`, `reusable-18-autofix.yml`, `reusable-20-pr-meta.yml`, `reusable-agents-issue-bridge.yml`, `reusable-agents-verifier.yml`, `reusable-codex-run.yml`, and `selftest-reusable-ci.yml`. + `reusable-16-agents.yml`, `reusable-18-autofix.yml`, `reusable-20-pr-meta.yml`, `reusable-agents-issue-bridge.yml`, `reusable-agents-verifier.yml`, `reusable-bot-comment-handler.yml`, `reusable-codex-run.yml`, and `selftest-reusable-ci.yml`. - **Triggers.** Invoked via `workflow_call` by Gate, Gate summary job, and manual reruns. `selftest-reusable-ci.yml` handles the nightly rehearsal (cron at 06:30Β UTC) and manual publication modes via `workflow_dispatch`. diff --git a/tests/workflows/test_workflow_naming.py b/tests/workflows/test_workflow_naming.py index b44eb5480..671595254 100644 --- a/tests/workflows/test_workflow_naming.py +++ b/tests/workflows/test_workflow_naming.py @@ -209,6 +209,7 @@ def test_workflow_display_names_are_unique(): "reusable-70-orchestrator-main.yml": "Agents 70 Main (Reusable)", "reusable-agents-issue-bridge.yml": "Reusable Agents Issue Bridge", "reusable-agents-verifier.yml": "Reusable Agents Verifier", + "reusable-bot-comment-handler.yml": "Reusable Bot Comment Handler", "selftest-reusable-ci.yml": "Selftest: Reusables", "selftest-ci.yml": "Selftest CI", }