Agent Skills: Monitor PR CI & Reviews

Monitors a PR's CI checks and review comments until all pass and issues are resolved. Auto-fixes CI failures, addresses inline review comments, replies, and resolves threads. Use when watching CI status, waiting for checks to pass, fixing CI errors, or resolving review feedback on a pull request.

UncategorizedID: onekeyhq/app-monorepo/1k-monitor-pr-ci

Repository

OneKeyHQLicense: NOASSERTION
2,337498

Install this agent skill to your local

pnpm dlx add-skill https://github.com/OneKeyHQ/app-monorepo/tree/HEAD/.skillshare/skills/1k-monitor-pr-ci

Skill Files

Browse the full folder contents for 1k-monitor-pr-ci.

Download Skill

Loading file tree…

.skillshare/skills/1k-monitor-pr-ci/SKILL.md

Skill Metadata

Name
1k-monitor-pr-ci
Description
Monitors a PR's CI checks and review comments until all pass and issues are resolved. Auto-fixes CI failures, addresses inline review comments, replies, and resolves threads. Use when watching CI status, waiting for checks to pass, fixing CI errors, or resolving review feedback on a pull request.

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:

  1. PR identifier (required if no current branch PR): PR number, GitHub PR URL
  2. 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

  1. Parse arguments: Split $ARGUMENTS into 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 $ARGUMENTS is 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
  2. 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)"'
      
  3. 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 checks only returns CI status. gh pr view --json reviews returns PR-level reviews but NOT inline file comments. gh api .../pulls/.../comments is 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:

  1. 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 name and link.
    • 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.
  2. Derive RUN_ID from the failed check link.

    • 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.
  3. Get failure log:

    gh run view <RUN_ID> --log-failed 2>&1 | tail -100
    
  4. Analyze the failure and determine the cause.

  5. 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
  6. 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/x or 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.
  7. 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 fixMUST 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:

  1. Read the file at the specified path and line
  2. Make the fix using the Edit tool
  3. 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:

  1. Stage changed files: git add <specific files>
  2. Commit with a descriptive message:
    git commit -m "fix: address PR review feedback
    
    - [list each fix made]"
    
  3. Push: git push

3f. Request re-review

After pushing fixes, request re-review from the reviewers who left comments:

  1. Get reviewers who left the comments (collected from Step 1b thread data, author.login fields)

  2. Request re-review:

    gh pr edit <PR_NUMBER> --add-reviewer <reviewer1>,<reviewer2>
    

    Or via API if --add-reviewer doesn't trigger re-review:

    gh api --method POST \
      repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers \
      -f 'reviewers[]=reviewer1' -f 'reviewers[]=reviewer2'
    
  3. 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:
    • gh CLI 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 — if CLOSED or MERGED, stop and inform the user)