Monitor PR CI & Reviews
Monitor a pull request's CI checks and review comments. Auto-fix CI failures, address inline review comments, reply to reviewers, and resolve threads.
Usage
/1k-monitor-pr-ci https://github.com/OneKeyHQ/app-monorepo/pull/10717 3m
/1k-monitor-pr-ci 10717 5m
/1k-monitor-pr-ci https://github.com/OneKeyHQ/app-monorepo/pull/10717
/1k-monitor-pr-ci 10717
/1k-monitor-pr-ci
Input
$ARGUMENTS — Two parts, space-separated:
- PR identifier (required if no current branch PR): PR number, GitHub PR URL
- Polling interval (optional, default
6m): e.g.30s,1m,2m,3分钟,5min,6m
If $ARGUMENTS is empty, auto-detect PR from current branch and use default 6m interval.
Step 0: Initial Setup
-
Parse arguments: Split
$ARGUMENTSinto PR identifier and polling interval.- PR identifier: a number or a URL like
https://github.com/{owner}/{repo}/pull/{number} - Polling interval: any token matching a time pattern (digits + time unit). Recognize:
s/sec/秒,m/min/分钟/分. Default:6m - If
$ARGUMENTSis empty, detect PR from current branch:gh pr list --head "$(git branch --show-current)" --json number --jq '.[0].number' - If no PR found and no argument provided, ask the user for the PR link
- PR identifier: a number or a URL like
-
Resolve owner/repo:
- If a full GitHub URL was provided, extract owner and repo from it
- Otherwise, detect from local repo:
gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"'
-
Confirm and start (no further questions needed):
Monitoring PR #10717 (OneKeyHQ/app-monorepo) Polling interval: 3m Starting...
Step 1: Poll Loop
Each iteration ([Check N/30]):
1a. Fetch ALL data — EVERY iteration
CRITICAL: You MUST run ALL FOUR queries in parallel on EVERY iteration. This is the most common failure mode — skipping query 3 or 4 on subsequent iterations causes new review comments to be silently ignored. Do NOT skip any query even if the previous iteration had no threads.
# 1. CI check status
gh pr checks <PR_NUMBER> --json bucket,name,state,link,startedAt,completedAt,workflow
# 2. PR-level reviews and general comments
gh pr view <PR_NUMBER> --json state,reviews,comments,reviewDecision,url
# 3. Inline review comments (e.g. from Devin, human reviewers)
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/comments \
--jq '.[] | {user: .user.login, body: .body, path: .path, line: .original_line, created_at: .created_at}'
# 4. Unresolved review threads (GraphQL — provides thread IDs for resolving)
gh api graphql -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
id
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
path
line
startLine
diffSide
comments(first: 20) {
nodes {
id
databaseId
body
author {
login
}
createdAt
}
}
}
}
}
}
}' -f owner="OWNER" -f repo="REPO" -F pr=PR_NUMBER
Why all four?
gh pr checksonly returns CI status.gh pr view --json reviewsreturns PR-level reviews but NOT inline file comments.gh api .../pulls/.../commentsis the ONLY way to get inline code review comments. The GraphQL query is the ONLY way to get thread IDs and resolution status. New comments can arrive at ANY time (from Devin, human reviewers, CI bots), so you MUST check on every iteration — not just the first.
GraphQL fallback: If GraphQL fails (e.g. token permission issues), fall back to the REST API for inline comments:
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
--jq '.[] | {id: .id, body: .body, path: .path, line: .original_line, user: .user.login, in_reply_to_id: .in_reply_to_id, created_at: .created_at}'
Note: REST fallback can fetch comments and reply to them, but cannot resolve threads (GitHub only supports this via GraphQL). When using REST fallback, skip the resolve step (Step 3d) and log a warning that threads must be resolved manually.
Filter to only unresolved threads (isResolved: false). Skip threads where the last comment is from the current gh user (already replied).
1b. Display status summary
[Check 3/30]
CI Status:
| Check | Status | Duration |
|------------------|---------|----------|
| lint (24.x) | pass | 5m34s |
| unittest (24.x) | pending | - |
Unresolved threads: 3
- src/views/Example.tsx:42 (@reviewer): "Consider using useCallback here"
- src/utils/format.ts:15 (@reviewer): "This should handle null case"
- src/components/Card.tsx:88 (@reviewer): "Typo in variable name"
1c. Decide next action
Before choosing an action, classify the gh pr checks output into three buckets:
- Normal failed checks: any failed check except
release-ready-merge-gate - Gate failure: failed check named exactly
release-ready-merge-gate - Pending checks: any check still pending or in progress
If release-ready-merge-gate is failing, treat it as an expected merge blocker rather than a CI failure to auto-fix.
| CI Status | Unresolved Threads | Action |
|-----------|-------------------|--------|
| Normal failed checks | - | Auto-fix CI failure (Step 2) |
| Gate failure | Has threads | Address threads (Step 3), then report blocked by missing release-ready |
| Gate failure | No threads | Stop waiting and report blocked by missing release-ready |
| Any pending | Has threads | Address threads (Step 3), keep waiting for CI |
| Any pending | No threads | Wait, re-check |
| All pass | Has threads | Address threads (Step 3) |
| All pass | No threads | Done (Step 4) |
Step 2: Auto-fix CI Failures
For each failed check:
-
Identify the actionable failed check from the latest poll result.
- Use the current iteration's
gh pr checks --json ...output, not stale output from a previous iteration. - Preserve the failed check's
nameandlink. - A rerun can expose a different failure later in the same check after an earlier error is fixed. For example,
lint (24.x)may fail first on formatting, then fail again on package-version consistency. If the same check name fails again after a push, treat it as a new Step 2 item and continue fixing until no normal failed checks remain.
- Use the current iteration's
-
Derive
RUN_IDfrom the failed checklink.- Example links:
https://github.com/{owner}/{repo}/actions/runs/<RUN_ID>/job/<JOB_ID>https://github.com/{owner}/{repo}/runs/<RUN_ID>
- Extract
<RUN_ID>from the URL and use that exact run when fetching logs.
- Example links:
-
Get failure log:
gh run view <RUN_ID> --log-failed 2>&1 | tail -100 -
Analyze the failure and determine the cause.
-
Fixable (lint error, type error, test failure from our changes):
- Fix the code
- Commit:
fix: resolve CI <check-name> failure - Push to PR branch
- Wait 30s, return to Step 1
-
Potentially unrelated or pre-existing (repo-wide package/version consistency failure, failure in files untouched by the PR, or issue that appears to predate the PR):
- Do a quick verification against
origin/xor inspect whether the failing condition already exists outside the PR diff. - If the same failure already exists on base, report it as a pre-existing blocker instead of pretending it was fixed by the PR work.
- Ask the user whether to fix the blocker in this PR anyway or leave it as an unrelated issue.
- Do a quick verification against
-
Not fixable (infra issue, flaky test, unrelated failure the user does not want fixed here):
- Report failure details to user
- Suggest actions (re-run, skip, manual fix)
- Ask user how to proceed
Step 3: Address Review Threads
For each unresolved thread:
3a. Categorize the comment
Read the comment body and the relevant code context. Categorize:
- Code fix needed — requires file modification → auto-fix
- Question — requires explanation only, no code change → auto-reply
- Disagree/Won't fix — MUST ask user before responding. Never auto-resolve disagreements.
Display a one-line log per thread of what will be done, then proceed to fix immediately. Do NOT wait for user confirmation — only pause for disagree/won't-fix cases.
3b. Fix the code
For each thread that needs a code fix:
- Read the file at the specified path and line
- Make the fix using the Edit tool
- Verify the fix doesn't break lint/types if quick to check
3c. Reply to the comment
Reply explaining what was done, using the REST API:
gh api --method POST \
repos/{owner}/{repo}/pulls/{pr_number}/comments/{comment_database_id}/replies \
-f body='Fixed: [concise explanation of the change]'
For questions (no code change needed):
gh api --method POST \
repos/{owner}/{repo}/pulls/{pr_number}/comments/{comment_database_id}/replies \
-f body='[answer to the question]'
Keep replies concise. Explain what was changed and why.
3d. Resolve the thread
After replying, resolve the thread via GraphQL:
gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread {
isResolved
}
}
}' -f threadId="THREAD_NODE_ID"
Note: Resolve thread requires GraphQL — there is no REST API equivalent. If Step 1b fell back to REST, skip this step and log a warning: "Thread resolve skipped (GraphQL unavailable). Please resolve manually."
3e. Commit and push
After all threads are addressed in this iteration:
- Stage changed files:
git add <specific files> - Commit with a descriptive message:
git commit -m "fix: address PR review feedback - [list each fix made]" - Push:
git push
3f. Request re-review
After pushing fixes, request re-review from the reviewers who left comments:
-
Get reviewers who left the comments (collected from Step 1b thread data,
author.loginfields) -
Request re-review:
gh pr edit <PR_NUMBER> --add-reviewer <reviewer1>,<reviewer2>Or via API if
--add-reviewerdoesn't trigger re-review:gh api --method POST \ repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers \ -f 'reviewers[]=reviewer1' -f 'reviewers[]=reviewer2' -
Return to Step 1 (wait for CI to re-run)
Step 3g: Report release-ready gate blocker
If the only remaining failed check is release-ready-merge-gate, stop the polling loop and report:
CI is complete, but merge is blocked by the release-ready gate.
Blocking check:
- release-ready-merge-gate: expected failure until the PR gets the `release-ready` label
Action:
- Add the `release-ready` label to the PR, then run the monitor again if needed
Do not:
- attempt to auto-fix this check
- treat it as flaky infra
- continue waiting for it to pass on its own
Step 4: Final Report
When all normal CI checks pass and no unresolved threads remain:
All normal CI checks passed. All review threads resolved.
CI:
| Check | Status | Duration |
|------------------|--------|----------|
| lint (24.x) | pass | 5m34s |
| unittest (24.x) | pass | 4m42s |
Review threads: 5 resolved, 0 remaining
PR: <URL>
Status: Ready for re-review / Ready to merge
If release-ready-merge-gate is still failing, do not use this final report. Use the blocked-state report from Step 3g instead.
Polling Rules
- Default 6 minutes between checks (user can customize in Step 0)
- 30 seconds after fix+push to allow CI restart
- Maximum 30 iterations, then ask user to continue or stop
- Always show
[Check N/30]
Important Notes
- CI failures: auto-fix without asking
- Do not stop after fixing the first CI error if the rerun exposes another normal failed check later. Continue Step 2 on every new failed-check result until all normal CI checks pass.
- Review comments: auto-fix without asking — display a brief summary of what will be done, then proceed immediately
- Disagree/Won't fix: ALWAYS ask user before replying or resolving — this is the ONLY case that requires user input
- Never force-push or amend commits
- Each fix round is a new commit
- Fix multiple CI failures in one commit when possible
- Do NOT re-run checks automatically (only if user requests
gh run rerun) - Do NOT include "Co-Authored-By" or "Generated with" in commit messages
- Track which threads have been addressed to avoid duplicate work across iterations
Error Handling
- Non-blocking errors: If any individual step fails (resolve thread, reply to comment, request re-review), log a warning and continue with the next thread/step. Never abort the entire loop due to a single thread failure.
- GraphQL unavailable: Fall back to REST API for fetching comments. Skip resolve step, log warning.
- Reply fails: Log warning with thread path/line, continue to next thread. The code fix is still committed.
- Resolve fails: Log warning, continue. The thread stays open but the fix is pushed.
- Re-review request fails: Log warning, continue. The reviewer can still see the push notification.
- Blocking errors: Abort the loop if:
ghCLI is not authenticated- The PR does not exist
- The PR is already closed or merged (check via
gh pr view <PR_NUMBER> --json state --jq '.state'each iteration — ifCLOSEDorMERGED, stop and inform the user)