Unverified 提交 8c39897d authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

feat: support cc:request:now label for immediate PR review trigger (#2618)

## Summary - Adds `pull_request: types: [labeled]` trigger to `pr-review-responder.yml` so that adding the `cc:request:now` label immediately runs the pr-fix flow without waiting for CI to complete - Refactors the script to handle both `pull_request` and `workflow_run` event types, extracting `head_repo`, `head_branch`, and `ci_conclusion` as shared step outputs - Gates the `needs-human` labeler steps on `ci_conclusion` availability (only from `workflow_run` events) since CI status is unknown for immediate triggers ## Test plan - [ ] Add `cc:request:now` label to a test PR and verify the workflow triggers immediately - [ ] Verify existing `cc:request` / `cc:request:N` labels still work via the `workflow_run` path - [ ] Verify the retry loop works: if Claude pushes commits from a `cc:request:now` trigger, confirm it sets `cc:request:1` and subsequent retries go through the normal CI-waiting flow - [ ] Verify the job-level `if` filter prevents unnecessary runs for unrelated label additions 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2618" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new `pull_request_target` label-triggered path to a high-permission workflow; while gated by allowed actors and PR author, mistakes here could expand when privileged automation runs or what code gets checked out/pushed. > > **Overview** > **Adds an immediate trigger for PR fix loops.** `pr-review-responder.yml` now runs not only after `CI` completes, but also when a trusted actor applies the `cc:request:now` label, so the automation can start without waiting for CI. > > **Refactors the workflow to support both event types safely.** The PR-info step now branches on event type to derive `pr_number`, `head_repo`, `head_branch`, and (only for `workflow_run`) `ci_conclusion`, uses these outputs for checkout and retry detection, and skips the needs-human labeler steps when CI conclusion is unavailable. > > **Docs/process updates.** Updates the workflows README to document the new trigger and removes outdated guidance about auto-adding `cc:request` from the `pr-push` command; adds a small rebase-conflict tip for documentation table merges. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7fa3a013b815dda47ab643fd9b23f51f6feac59b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Enable immediate PR review runs via the cc:request:now label, using pull_request_target with strict labeler and author gating. The existing cc:request/cc:request:N CI-driven flow stays the same. - **New Features** - Trigger pr-review-responder on pull_request_target labeled when the label is cc:request:now. - Guarded start with a job-level filter; only labels applied by wwwillchen, wwwillchen-bot, azizmejri1, or princeaden1 are allowed, and only PRs authored by wwwillchen/wwwillchen-bot are eligible. - **Refactors** - Support both pull_request_target and workflow_run; expose pr_number, head_repo, head_branch, and ci_conclusion as shared outputs and use them for checkout/status. - Gate needs-human labeling on ci_conclusion (workflow_run only); cc:request:now skips it. Add null checks for head_repository and exclude cc:request:now in workflow_run label checks to avoid race conditions. - Docs: update workflow trigger README, remove outdated label instructions in .claude/commands/dyad/pr-push.md, and add rebase guidance to keep both additions in README tables. <sup>Written for commit 7fa3a013b815dda47ab643fd9b23f51f6feac59b. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
上级 ea61dc53
......@@ -170,17 +170,6 @@ Commit any uncommitted changes, run lint checks, fix any issues, and push the cu
Use the commit messages and changed files to write a good title and summary.
**Add labels for non-trivial PRs:**
After creating or verifying the PR exists, assess whether the changes are non-trivial:
- Non-trivial = more than simple typo fixes, formatting, or config changes
- Non-trivial = any code logic changes, new features, bug fixes, refactoring
For non-trivial PRs, add the `cc:request` label to request code review:
```
gh pr edit --add-label "cc:request"
```
9. **Summarize the results:**
- Report if a new feature branch was created (and its name)
- Report any uncommitted changes that were committed in step 2
......
......@@ -56,7 +56,7 @@ flowchart TD
| `merge-pr.yml` | `Merge PR when ready` | Auto-merges eligible PRs after successful CI when all checks pass. | `workflow_run` for `CI` on `completed` (successful PR-triggered CI only). | None (reads `merge-when-ready`, does not set labels). |
| `nightly-runner-cleanup.yml` | `Nightly Runner Cleanup` | Safely frees disk space on self-hosted runner ci1 (caches, npm, runner \_work). | Daily cron (4 AM PST); or `workflow_dispatch`. | None. |
| `playwright-comment.yml` | `Playwright Report Comment` | Posts a Playwright summary comment on the PR tied to a completed CI run. | `workflow_run` for `CI` on `completed`. | None. |
| `pr-review-responder.yml` | `PR Review Responder` | Runs Claude fix loops for trusted PRs, retriggers checks/reviews, and advances request-state labels. | `workflow_run` for `CI` on `completed`. | `cc:request`/`cc:request:N` -> `cc:pending`; then `cc:request:N+1` on pushed commits, `cc:done` on clean finish, `cc:failed` on failure; may add `needs-human:review-issue` when retries exhausted. |
| `pr-review-responder.yml` | `PR Review Responder` | Runs Claude fix loops for trusted PRs, retriggers checks/reviews, and advances request-state labels. | `pull_request_target` on `labeled` (only `cc:request:now`); `workflow_run` for `CI` on `completed`. | `cc:request`/`cc:request:N` -> `cc:pending`; then `cc:request:N+1` on pushed commits, `cc:done` on clean finish, `cc:failed` on failure; may add `needs-human:review-issue` when retries exhausted. |
| `pr-status-labeler.yml` | `PR Status Labeler` | Applies human-attention labels based on CI outcome and review freshness/verdict. | `workflow_run` for `CI` on `completed`. | Swaps between `needs-human:final-check` (clean + passing) and `needs-human:review-issue` (failing/stale/missing/issueful review). |
| `release.yml` | `Release app` | Manually builds and publishes signed release artifacts across platforms, then verifies assets. | `workflow_dispatch`. | None. |
......
......@@ -4,6 +4,8 @@
name: PR Review Responder
on:
pull_request_target:
types: [labeled]
workflow_run:
workflows: ["CI"]
# Regardless of success or fail, we want to run this workflow.
......@@ -16,6 +18,9 @@ on:
jobs:
respond-to-pr:
if: >-
github.event_name == 'workflow_run' ||
(github.event_name == 'pull_request_target' && github.event.label.name == 'cc:request:now')
environment: ai-bots
runs-on: ubuntu-latest
permissions:
......@@ -28,62 +33,100 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const run = context.payload.workflow_run;
const eventName = context.eventName;
let prNumber, headRepo, headBranch, prLabels, prAuthor;
// Get PR associated with this workflow run
// Note: run.pull_requests can be empty for cross-repo PRs (forks)
// So we also search by head SHA as a fallback
let prNumber;
if (eventName === 'pull_request_target') {
// Triggered by adding cc:request:now label
const pr = context.payload.pull_request;
prNumber = pr.number;
headRepo = pr.head.repo ? pr.head.repo.full_name : null;
headBranch = pr.head.ref;
prLabels = pr.labels;
prAuthor = pr.user.login;
if (run.pull_requests && run.pull_requests.length > 0) {
prNumber = run.pull_requests[0].number;
} else {
// Search for PRs with matching head branch
// Note: head_repository can be null if the fork was deleted
if (!run.head_repository) {
// Check that the person who applied the label is a trusted actor
const actor = context.actor;
const allowedActors = ['wwwillchen', 'wwwillchen-bot'];
if (!allowedActors.includes(actor)) {
console.log(`Label applied by ${actor} who is not in the allowed actors list`);
core.setOutput('should_continue', 'false');
return;
}
if (!headRepo) {
console.log('Head repository not available');
core.setOutput('should_continue', 'false');
return;
}
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${run.head_repository.owner.login}:${run.head_branch}`
});
} else {
// workflow_run event
const run = context.payload.workflow_run;
// Get PR associated with this workflow run
// Note: run.pull_requests can be empty for cross-repo PRs (forks)
// So we also search by head SHA as a fallback
if (run.pull_requests && run.pull_requests.length > 0) {
prNumber = run.pull_requests[0].number;
} else {
// Search for PRs with matching head branch
// Note: head_repository can be null if the fork was deleted
if (!run.head_repository) {
console.log('Head repository not available');
core.setOutput('should_continue', 'false');
return;
}
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${run.head_repository.owner.login}:${run.head_branch}`
});
if (prs.length === 0) {
console.log('No pull requests found for this workflow run');
if (prs.length === 0) {
console.log('No pull requests found for this workflow run');
core.setOutput('should_continue', 'false');
return;
}
prNumber = prs[0].number;
}
if (!run.head_repository) {
console.log('Head repository not available (fork may have been deleted)');
core.setOutput('should_continue', 'false');
return;
}
prNumber = prs[0].number;
}
// Fetch full PR details to get labels
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
headRepo = run.head_repository.full_name;
headBranch = run.head_branch;
// Fetch full PR details to get labels and author
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
prLabels = pr.labels;
prAuthor = pr.user.login;
}
// Only allow wwwillchen and wwwillchen-bot to use this workflow
if (pr.user.login !== 'wwwillchen' && pr.user.login !== 'wwwillchen-bot') {
console.log(`PR #${prNumber} author ${pr.user.login} is not allowed to use this workflow`);
if (prAuthor !== 'wwwillchen' && prAuthor !== 'wwwillchen-bot') {
console.log(`PR #${prNumber} author ${prAuthor} is not allowed to use this workflow`);
core.setOutput('should_continue', 'false');
return;
}
// Check for cc:request (initial) or cc:request:N (re-request) labels
// Check for cc:request, cc:request:now (initial) or cc:request:N (re-request) labels
// If multiple labels exist, use the highest count to avoid resetting the retry counter
let requestCount = -1;
let currentLabel = '';
for (const label of pr.labels) {
if (label.name === 'cc:request') {
for (const label of prLabels) {
if (label.name === 'cc:request' || (label.name === 'cc:request:now' && eventName === 'pull_request_target')) {
if (requestCount < 0) {
requestCount = 0;
currentLabel = 'cc:request';
currentLabel = label.name;
}
}
const match = label.name.match(/^cc:request:(\d+)$/);
......@@ -97,7 +140,7 @@ jobs:
}
if (requestCount === -1) {
console.log(`PR #${prNumber} does not have a cc:request or cc:request:N label`);
console.log(`PR #${prNumber} does not have a cc:request, cc:request:now, or cc:request:N label`);
core.setOutput('should_continue', 'false');
return;
}
......@@ -147,7 +190,12 @@ jobs:
core.setOutput('should_continue', 'true');
core.setOutput('request_count', requestCount);
core.setOutput('current_label', currentLabel);
core.setOutput('head_repo', run.head_repository.full_name);
core.setOutput('head_repo', headRepo);
core.setOutput('head_branch', headBranch);
// ci_conclusion is only available for workflow_run events
core.setOutput('ci_conclusion', eventName === 'workflow_run'
? context.payload.workflow_run.conclusion
: '');
- name: Checkout repository
if: steps.pr-info.outputs.should_continue == 'true'
......@@ -157,8 +205,8 @@ jobs:
# checks out code from whoever last pushed. If "Allow edits from maintainers" is
# enabled, other maintainers could push to the branch. This is acceptable because
# maintainers already have write access to the repo and are trusted.
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_branch }}
repository: ${{ steps.pr-info.outputs.head_repo }}
ref: ${{ steps.pr-info.outputs.head_branch }}
fetch-depth: 0
- name: Record HEAD before Claude Code
......@@ -215,8 +263,8 @@ jobs:
PR_HEAD_SHA=$(gh pr view ${{ steps.pr-info.outputs.pr_number }} --repo ${{ github.repository }} --json headRefOid --jq '.headRefOid')
if [ -z "$PR_HEAD_SHA" ]; then
echo "Failed to get PR head SHA, falling back to git fetch"
git fetch origin ${{ github.event.workflow_run.head_branch }} || git fetch origin
PR_HEAD_SHA=$(git rev-parse FETCH_HEAD 2>/dev/null || git rev-parse origin/${{ github.event.workflow_run.head_branch }})
git fetch origin ${{ steps.pr-info.outputs.head_branch }} || git fetch origin
PR_HEAD_SHA=$(git rev-parse FETCH_HEAD 2>/dev/null || git rev-parse origin/${{ steps.pr-info.outputs.head_branch }})
fi
if [ "${{ steps.before-claude.outputs.sha }}" != "$PR_HEAD_SHA" ]; then
......@@ -253,11 +301,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout base repo for labeler script
# The earlier checkout (line 186) checks out the PR head repo, which for
# The earlier checkout checks out the PR head repo, which for
# fork PRs may not contain scripts/pr-status-labeler.js. Checkout the
# base repo's default branch to a separate path so the script is always available.
# Only runs when ci_conclusion is available (workflow_run events).
if: >-
steps.pr-info.outputs.should_continue == 'true' &&
steps.pr-info.outputs.ci_conclusion != '' &&
always() &&
steps.retrigger.outputs.commits_pushed != 'true'
uses: actions/checkout@v5
......@@ -268,9 +318,11 @@ jobs:
- name: Apply needs-human status label
# Run when PR reaches a terminal state (cc:done or cc:failed) but NOT when
# commits were pushed (retry loop continues). Uses always() to run even if
# the Claude Code step failed.
# the Claude Code step failed. Only runs when ci_conclusion is available
# (workflow_run events); cc:request:now triggers skip this since CI status is unknown.
if: >-
steps.pr-info.outputs.should_continue == 'true' &&
steps.pr-info.outputs.ci_conclusion != '' &&
always() &&
steps.retrigger.outputs.commits_pushed != 'true'
uses: actions/github-script@v7
......@@ -282,5 +334,5 @@ jobs:
context,
core,
prNumber: parseInt('${{ steps.pr-info.outputs.pr_number }}', 10),
ciConclusion: '${{ github.event.workflow_run.conclusion }}'
ciConclusion: '${{ steps.pr-info.outputs.ci_conclusion }}'
});
......@@ -43,6 +43,7 @@ gh api repos/dyad-sh/dyad/issues/{PR_NUMBER}/labels -f "labels[]=label-name"
- When resolving import conflicts (e.g., `<<<<<<< HEAD` with different imports), keep **both** imports if both are valid and needed by the component
- When resolving conflicts in i18n-related commits, watch for duplicate constant definitions that conflict with imports from `@/lib/schemas` (e.g., `DEFAULT_ZOOM_LEVEL`)
- If both sides of a conflict have valid imports/hooks, keep both and remove any duplicate constant redefinitions
- When rebasing documentation/table conflicts (e.g., workflow README tables), prefer keeping **both** additions from HEAD and upstream - merge new rows/content from both branches rather than choosing one side
## Rebasing with uncommitted changes
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论