[Feature]: Deterministic Run Replay for Reliable Agent Debugging #9261
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: PR Check Command | |
| on: | |
| issue_comment: | |
| types: [created] | |
| jobs: | |
| check-pr: | |
| # Only run on PR comments that start with /check | |
| if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/check') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| issues: write | |
| checks: write | |
| statuses: write | |
| steps: | |
| - name: Check PR requirements | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prNumber = context.payload.issue.number; | |
| console.log(`Triggered by /check comment on PR #${prNumber}`); | |
| // Fetch PR data | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| }); | |
| const prBody = pr.body || ''; | |
| const prTitle = pr.title || ''; | |
| const prAuthor = pr.user.login; | |
| const headSha = pr.head.sha; | |
| // Create a check run in progress | |
| const { data: checkRun } = await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'check-requirements', | |
| head_sha: headSha, | |
| status: 'in_progress', | |
| started_at: new Date().toISOString(), | |
| }); | |
| // Extract issue numbers | |
| const issuePattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)?\s*#(\d+)/gi; | |
| const allText = `${prTitle} ${prBody}`; | |
| const matches = [...allText.matchAll(issuePattern)]; | |
| const issueNumbers = [...new Set(matches.map(m => parseInt(m[1], 10)))]; | |
| console.log(`PR #${prNumber}:`); | |
| console.log(` Author: ${prAuthor}`); | |
| console.log(` Found issue references: ${issueNumbers.length > 0 ? issueNumbers.join(', ') : 'none'}`); | |
| if (issueNumbers.length === 0) { | |
| const message = `## PR Closed - Requirements Not Met | |
| This PR has been automatically closed because it doesn't meet the requirements. | |
| **Missing:** No linked issue found. | |
| **To fix:** | |
| 1. Create or find an existing issue for this work | |
| 2. Assign yourself to the issue | |
| 3. Re-open this PR and add \`Fixes #123\` in the description | |
| **Why is this required?** See #472 for details.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: message, | |
| }); | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| state: 'closed', | |
| }); | |
| // Update check run to failure | |
| await github.rest.checks.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| check_run_id: checkRun.id, | |
| status: 'completed', | |
| conclusion: 'failure', | |
| completed_at: new Date().toISOString(), | |
| output: { | |
| title: 'Missing linked issue', | |
| summary: 'PR must reference an issue (e.g., `Fixes #123`)', | |
| }, | |
| }); | |
| core.setFailed('PR must reference an issue'); | |
| return; | |
| } | |
| // Check if PR author is assigned to any linked issue | |
| let issueWithAuthorAssigned = null; | |
| let issuesWithoutAuthor = []; | |
| for (const issueNum of issueNumbers) { | |
| try { | |
| const { data: issue } = await github.rest.issues.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNum, | |
| }); | |
| const assigneeLogins = (issue.assignees || []).map(a => a.login); | |
| if (assigneeLogins.includes(prAuthor)) { | |
| issueWithAuthorAssigned = issueNum; | |
| console.log(` Issue #${issueNum} has PR author ${prAuthor} as assignee`); | |
| break; | |
| } else { | |
| issuesWithoutAuthor.push({ | |
| number: issueNum, | |
| assignees: assigneeLogins | |
| }); | |
| console.log(` Issue #${issueNum} assignees: ${assigneeLogins.length > 0 ? assigneeLogins.join(', ') : 'none'}`); | |
| } | |
| } catch (error) { | |
| console.log(` Issue #${issueNum} not found`); | |
| } | |
| } | |
| if (!issueWithAuthorAssigned) { | |
| const issueList = issuesWithoutAuthor.map(i => | |
| `#${i.number} (assignees: ${i.assignees.length > 0 ? i.assignees.join(', ') : 'none'})` | |
| ).join(', '); | |
| const message = `## PR Closed - Requirements Not Met | |
| This PR has been automatically closed because it doesn't meet the requirements. | |
| **PR Author:** @${prAuthor} | |
| **Found issues:** ${issueList} | |
| **Problem:** The PR author must be assigned to the linked issue. | |
| **To fix:** | |
| 1. Assign yourself (@${prAuthor}) to one of the linked issues | |
| 2. Re-open this PR | |
| **Why is this required?** See #472 for details.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: message, | |
| }); | |
| await github.rest.pulls.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| state: 'closed', | |
| }); | |
| // Update check run to failure | |
| await github.rest.checks.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| check_run_id: checkRun.id, | |
| status: 'completed', | |
| conclusion: 'failure', | |
| completed_at: new Date().toISOString(), | |
| output: { | |
| title: 'PR author not assigned to issue', | |
| summary: `PR author @${prAuthor} must be assigned to one of the linked issues: ${issueList}`, | |
| }, | |
| }); | |
| core.setFailed('PR author must be assigned to the linked issue'); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: `✅ PR requirements met! Issue #${issueWithAuthorAssigned} has @${prAuthor} as assignee.`, | |
| }); | |
| // Update check run to success | |
| await github.rest.checks.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| check_run_id: checkRun.id, | |
| status: 'completed', | |
| conclusion: 'success', | |
| completed_at: new Date().toISOString(), | |
| output: { | |
| title: 'Requirements met', | |
| summary: `Issue #${issueWithAuthorAssigned} has @${prAuthor} as assignee.`, | |
| }, | |
| }); | |
| console.log(`PR requirements met!`); | |
| } |