| name | watch |
| description | Poll a GitHub PR for new comments and PR reviews and act on them autonomously. Use when the user wants to monitor a PR for feedback and have Claude implement requested changes automatically. |
| argument-hint | [PR_URL] [--no-automerge] [--max-turns <N>] [--no-notify] |
Watch PR for Comments and Reviews
Poll a GitHub Pull Request for all comments (general and inline review comments) and PR reviews (top-level review submissions) and autonomously act on each one.
This skill uses shell scripts for all mechanical bookkeeping (polling, fetching, deduplication, idle tracking, checklist parsing, wrap-up generation) and only invokes the agent for tasks requiring AI reasoning (implementing changes, resolving conflicts).
Architecture
watch-poll.sh — Deterministic polling loop (no AI needed)
├── Fetches comments from 3 GitHub API endpoints
├── Deduplicates by tracking last-seen IDs
├── Merges from main, detects conflicts
├── Parses PR body for unchecked Claude Code checklist items
├── Writes work-items.json when there's actionable work
└── Generates wrap-up comment from action-log.json on idle timeout **or** when `--max-turns` is satisfied (see below)
watch-post.sh — Post-session tasks (no AI needed)
└── Auto-merges the PR when enabled (CI runs on the PR via Actions but is not a merge gate here)
watch-append-wrapup-ci.sh — Optional: append remediation notes to the wrap-up comment (legacy; `/fix-ci` drives CI remediation)
Agent (this skill) — Only invoked when reasoning is needed
├── Implements review comment requests
├── Resolves merge conflicts
└── Appends actions to action-log.json
Setup
Parse the arguments from: $ARGUMENTS
Extract any PR URL and flags. The format is: [PR_URL] [--no-automerge] [--max-turns <N>] [--no-notify]
The PR URL is optional. If not provided, detect it automatically from the current branch:
PR_URL=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh pr view --json url --jq '.url' 2>/dev/null || echo "")
If auto-detection fails (no PR exists for the current branch), abort with a clear error message.
Parse the PR number from the URL (e.g., https://github.com/supersuit-tech/permission-slip/pull/123 → 123).
Set these variables for the session:
PR_URL — the PR URL (from arguments or auto-detected from current branch)
PR_NUMBER — the extracted PR number
AUTO_MERGE — true by default, false if --no-automerge was passed
MAX_TURNS — the value of --max-turns if passed, 0 otherwise (0 = unlimited)
NO_NOTIFY — true if --no-notify was passed, false otherwise
GH_CMD — GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh
SKILL_DIR — the directory containing this skill file (.claude/skills/watch)
Preserve WORK_DIR from watch-poll.sh session JSON for the whole session — it holds action-log.json, the wrap-up comment id file, and other state. The poll script writes pr-number.txt and wrapup-comment-id when it posts the wrap-up.
Orchestration Loop
The agent orchestrates the session by running the shell scripts and only doing AI work when the scripts signal that it's needed.
Step 1: Run the polling script
bash "${SKILL_DIR}/watch-poll.sh" "${PR_URL}" $([[ "$AUTO_MERGE" == "false" ]] && echo "--no-automerge") $([[ "$MAX_TURNS" -gt 0 ]] && echo "--max-turns $MAX_TURNS") 2>&1
bash "${SKILL_DIR}/watch-poll.sh" "${PR_URL}" $([[ "$AUTO_MERGE" == "false" ]] && echo "--no-automerge") $([[ "$MAX_TURNS" -gt 0 ]] && echo "--max-turns $MAX_TURNS") --work-dir "$WORK_DIR" 2>&1
The script handles:
- Fetching comments from all 3 GitHub API endpoints (reviews, review comments, issue comments)
- Deduplication via last-seen ID tracking per endpoint
- Merging from main and detecting conflicts
- Parsing the PR body for unchecked
### Claude Code checklist items
- Initial delay before first poll (10 minutes by default, skipped on re-invocations)
- Idle counter tracking (10 consecutive empty cycles = timeout)
- Turn limit tracking (exits when
--max-turns agent invocations reached)
- Generating and posting the wrap-up comment on idle timeout or at the start of the next poll after the turn counter reaches
--max-turns
Step 2: Check script output
The script communicates via stdout signals and exit codes:
Exit code 0 + AGENT_NEEDED: The script found actionable work. Read WORK_ITEMS_FILE for the structured work items:
{
"pr_number": "123",
"pr_url": "https://github.com/.../pull/123",
"branch": "feature-branch",
"cycle": 1,
"comments": [
{
"type": "review_comment",
"id": 456,
"author": "reviewer",
"body": "Please rename this variable",
"path": "src/main.go",
"line": 42,
"diff_hunk": "...",
"node_id": "..."
}
],
"merge_status": "clean|updated|conflict",
"conflict_files": ["file1.go", "file2.ts"],
"checklist_items": [
{"type": "checklist", "text": "Add unit tests for the new handler"}
]
}
Exit code 0 + IDLE_TIMEOUT: The session ended due to inactivity or the --max-turns limit being reached. The script already posted the wrap-up comment (and recorded its id under WORK_DIR for later edits). Proceed to Step 5 (post-session / CI loop).
Critical: AGENT_NEEDED is not the end of the session
When the script prints AGENT_NEEDED and exits 0, it has not posted the wrap-up and has not emitted IDLE_TIMEOUT. The orchestrator must run Step 1 again with the same --work-dir (and the same --max-turns if any) after finishing Step 3 — unless the user explicitly asked to stop mid-session.
-
Why: On an AGENT_NEEDED exit, watch-poll.sh increments turns-count in WORK_DIR and exits. The next invocation either (a) hits idle after empty poll cycles and posts wrap-up + IDLE_TIMEOUT, or (b) with --max-turns N, sees turns-count >= N at the start of that next run, posts wrap-up, then prints IDLE_TIMEOUT immediately (no extra agent work). Skipping that second poll is why wrap-up and watch-post.sh never ran.
-
--max-turns 1: Expect two poll runs in the common case: first run → AGENT_NEEDED (do work); second run → IDLE_TIMEOUT (wrap-up posted) → Step 5.
Reliability: deferred ID advancement and pending queue
watch-poll.sh uses deferred ID advancement to prevent data loss. When fetch_new_comments() finds new comments, it writes their max IDs to staged files (.staged-*), not the actual last-*-id files. The staged IDs are only committed (moved to actual files) after build_work_items() has safely persisted them to work-items.json. This means:
- If the script crashes between fetching and building work items, the next invocation re-fetches the same comments (IDs haven't advanced).
- On
AGENT_NEEDED exit, the script saves a pending queue (pending-items.json) recording which comments were handed off. On re-invocation, it checks whether the action log grew (indicating the agent processed the items). If not, the pending comments are re-included in the next cycle.
- The idle counter never increments when the pending queue is non-empty — this prevents
IDLE_TIMEOUT from firing when there's unprocessed work.
Post-idle drain for unresolved threads
Before emitting IDLE_TIMEOUT, the script performs a drain check: it queries the PR's review threads via GraphQL and looks for any that are still unresolved. If unresolved threads exist, it synthesizes one more AGENT_NEEDED pass with those threads as work items. This handles the case where comments landed between the last AGENT_NEEDED and the idle timeout, or where the PR was merged while review threads remained open.
Exit code 100: Pre-poll merge conflict. The script detected conflicts before the polling loop started. Read WORK_ITEMS_FILE for conflict details and resolve them (see Step 3), then re-run the polling script.
Step 3: Process work items (AI reasoning)
For each item in work-items.json, apply the appropriate action:
Comments and Reviews
Default: Parallel processing. When the comments array contains multiple items, process them in parallel using the Agent tool. Each comment gets its own subagent running in an isolated worktree, so they can't conflict with each other during implementation. After all agents complete, cherry-pick their commits onto the current branch, resolve any conflicts, and run tests once.
Parallel processing flow (2+ comments):
- Group comments by file. If multiple comments target the same file (same
path), group them together — they must be handled by a single agent to avoid edit conflicts. Comments on different files (or general PR-level comments) can each get their own agent.
- Spawn one Agent per group using the Agent tool with
isolation: "worktree". Each agent receives:
- The comment(s) it's responsible for (body, path, line, diff_hunk, node_id)
- The PR number and GH command prefix for resolving threads and reacting
- Instructions to: read the code, implement the change, commit, react to the comment, reply in the thread, and resolve the conversation
- The action log format so it can return structured log entries
- Launch all agents in a single message (parallel tool calls) so they run concurrently.
- After all agents complete, collect their results:
- For each agent that made changes in a worktree, cherry-pick or merge its commits onto the current branch
- If cherry-picks conflict, resolve the conflicts (prefer the change that matches the reviewer's intent)
- Collect action log entries from each agent's output
- Run tests once after all changes are integrated (
make test-backend for Go, make test-frontend for frontend, make test if unsure).
- Append all action log entries to the action log file.
Agent prompt template — each parallel agent should receive a prompt like:
You are handling a PR review comment for PR #<PR_NUMBER> on supersuit-tech/permission-slip.
Comment by @<AUTHOR>:
> <BODY>
File: <PATH> (line <LINE>)
Diff context:
<DIFF_HUNK>
Instructions:
1. Read the file and surrounding context to understand the code.
2. Implement the requested change. Commit with a descriptive message.
3. React to the comment and reply in the thread:
- React with +1: GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api repos/supersuit-tech/permission-slip/pulls/comments/<COMMENT_ID>/reactions -f content="+1"
- Reply: GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api repos/supersuit-tech/permission-slip/pulls/comments/<COMMENT_ID>/replies -f body="<your reply>"
4. Resolve the review thread using the GraphQL API (thread node_id: <NODE_ID>).
5. Return a JSON action log entry: {"type": "implemented", "author": "<AUTHOR>", "request": "<summary>", "commit": "<hash>"}
Or if you disagree: {"type": "declined", "author": "<AUTHOR>", "request": "<summary>", "reason": "<explanation>"}
Single comment flow (1 comment) or fallback: Process inline without spawning a subagent:
- Read the instruction in the comment body.
- Identify the file and line it's attached to (for inline review comments — use
path and line fields). For PR reviews, the body applies to the PR as a whole — check state for context (CHANGES_REQUESTED signals required fixes). For issue comments (normal PR conversation), treat them as general feedback that may reference specific files or areas of the code.
- Decide whether to act on it or disagree:
- If you agree: Implement the change. Commit with a clear message referencing the comment.
- If you disagree: Leave a reply on that comment thread explaining your reasoning.
- Do not ask questions — make your best judgment and implement.
- After implementing: Run relevant tests (
make test-backend for Go, make test-frontend for frontend, make test if unsure).
- Resolve the conversation using the GitHub GraphQL API:
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "THREAD_NODE_ID"}) {
thread {
isResolved
}
}
}'
To get the thread node ID, query for PR review threads and match by databaseId:
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api graphql -f query='
query {
repository(owner: "supersuit-tech", name: "permission-slip") {
pullRequest(number: PR_NUMBER) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
body
databaseId
}
}
}
}
}
}
}'
- Log the action by appending to the action log file (see Action Log Format below).
Merge Conflicts
If merge_status is "conflict" and conflict_files is non-empty:
- Run
git diff --name-only --diff-filter=U to list all conflicted files.
- For each conflicted file:
a. Read the entire file to understand the full context — not just the conflict markers.
b. Read the PR diff (
git diff origin/main..HEAD -- <file>) to understand what this branch intended to change.
c. Read the base branch version (git show origin/main:<file>) to understand what changed upstream.
d. Understand intent from both sides — check recent commit messages on both sides for context.
e. Resolve the conflict by editing the file to preserve the intent of both sides. Do NOT blindly accept "ours" or "theirs". If changes are truly incompatible, prefer the PR branch's version but note this in the action log.
f. Stage the resolved file with git add <file>.
- Complete the merge commit:
git commit -m "Merge origin/main: resolve conflicts in <list of files>"
- Run tests (
make test) and build (make build) to verify the resolution.
- Log conflict resolutions in the action log.
If the conflict cannot be resolved confidently, abort the merge (git merge --abort), post a comment on the PR explaining why, and log it as an open question.
Checklist Items
For each item in the checklist_items array:
- Read and understand the checklist item text.
- Implement the requested change (adding tests, fixing lint, updating docs, etc.).
- Run relevant tests to verify.
- Commit with a clear message referencing the checklist item.
- Check off the item in the PR body:
CURRENT_BODY=$(GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api "/repos/supersuit-tech/permission-slip/pulls/${PR_NUMBER}" --jq '.body')
UPDATED_BODY=$(echo "$CURRENT_BODY" | sed 's/- \[ \] <exact item text>/- [x] <exact item text>/')
GH_HOST=github.com GH_REPO=supersuit-tech/permission-slip gh api "/repos/supersuit-tech/permission-slip/pulls/${PR_NUMBER}" -X PATCH -f body="$UPDATED_BODY"
Update the PR body after each item to avoid race conditions.
- Log the action in the action log.
Decision Log
For anything you considered but chose not to do, or had questions about, or made a judgment call on:
- Find the GitHub issue linked to the PR (or the PR description itself).
- Append a checklist to the issue body under a
## Decision Log section with entries like:
- [ ] Considered X but chose Y because Z (commit abc123)
- [ ] Question: Should we also handle edge case X?
Push Changes
After processing all work items, push your commits:
git push -u origin <current-branch>
Step 3b: Update the action log
After processing all work items, append entries to the action log file (ACTION_LOG_FILE from the script output). The agent must read the current log, append new entries, and write it back.
Action Log Format
The action log is a JSON array. Each entry has a type field and type-specific fields:
[
{
"type": "change",
"description": "Rename variable for clarity",
"detail": "Renamed `usr` to `currentUser` per reviewer request",
"commit": "abc1234"
},
{
"type": "implemented",
"author": "reviewer-username",
"request": "rename the variable",
"commit": "abc1234"
},
{
"type": "declined",
"author": "reviewer-username",
"request": "add caching here",
"reason": "premature optimization, no performance issue observed"
},
{
"type": "conflict_resolution",
"file": "src/main.go",
"detail": "branch added handler, main renamed package — kept both changes",
"commit": "def5678"
},
{
"type": "checklist_done",
"text": "Add unit tests for the new handler",
"detail": "Added 3 test cases covering success, auth failure, and validation",
"commit": "ghi9012"
},
{
"type": "checklist_skipped",
"text": "Get stakeholder sign-off",
"reason": "requires human action / OpenClaw task"
},
{
"type": "judgment",
"description": "Whether to split the handler file",
"choice": "split into handler.go and handler_test.go",
"reason": "file was over 500 lines, splitting improves maintainability"
},
{
"type": "open_question",
"description": "Should we also add rate limiting to this endpoint?"
},
{
"type": "ci_remediation",
"workflow": "ci.yml",
"conclusion": "failure",
"detail": "go test ./db/... failed: missing migration grant — added GRANT in migration 20260320…",
"commit": "abc1234"
},
{
"type": "audit_remediation",
"workflow": "audit.yml",
"conclusion": "failure",
"detail": "gosec flagged unsafe usage — refactored to use sanitized input",
"commit": "def5678"
},
{
"type": "ci_fix_exhausted",
"workflow": "ci.yml",
"detail": "Stopped after 15 remediation attempts; integration test still flakes on job X — needs human triage"
}
]
You may still log ci_remediation / audit_remediation / ci_fix_exhausted if you manually fixed something worth recording; watch-append-wrapup-ci.sh can append those under ## 🔧 CI / audit remediation on the wrap-up comment. For driving CI to green on a branch, use the /fix-ci skill instead of this watch flow.
Step 4: Loop back to polling
After the agent finishes processing work items, go back to Step 1 and run the polling script again. The script maintains state across invocations via temp files in its work directory.
Important: Pass the same work directory to the script on subsequent runs so it preserves last-seen IDs and the action log. The script's WORK_DIR is printed in its session context output — capture it on the first run and reuse it.
Do not stop after one AGENT_NEEDED: Keep looping Step 1 → (Step 2–3 only when needed) until Step 2 shows IDLE_TIMEOUT. Only then run Step 5 (watch-post.sh). Stopping after a single agent turn is an orchestration failure, not a successful watch completion.
Step 5: Post-session — merge (optional)
When the polling script exits with IDLE_TIMEOUT, the wrap-up comment has already been posted.
CI and audit run automatically on every push to the PR via GitHub Actions (see .github/workflows/ci.yml, audit.yml, mobile-ci.yml). They are not required checks for merge in this flow: watch-post.sh does not wait on workflows or exit with failure when CI is red.
-
Run post-session. Always pass --work-dir "$WORK_DIR" (same session directory as watch-poll.sh):
bash "${SKILL_DIR}/watch-post.sh" "${PR_URL}" --work-dir "$WORK_DIR" $([[ "$AUTO_MERGE" == "false" ]] && echo "--no-automerge") 2>&1
-
Exit code 0 — Post-session finished. With AUTO_MERGE=true, the script attempts gh pr merge immediately.
Merge result (when AUTO_MERGE=true):
"[post] PR merged successfully." → The merge completed. Report it as merged, not "attempted".
"[post] Auto-merge failed." → The merge failed (for example branch protection still requires human-approved reviews or required checks that are not satisfied). The script posts a PR comment. Fix blockers out of band or merge manually.
If you need CI to be green before shipping: use the /fix-ci skill on the branch, or fix failures locally and push; do not expect /watch to loop on CI outcomes.
Do NOT hedge when the script output clearly indicates success or failure.
Important Rules
- Never ask for human input — decide and implement autonomously.
- Process ALL comments and reviews — don't skip any, even if multiple arrive between polls.
- Commit frequently — one commit per comment or logical group of related comments.
- Run tests before pushing to make sure nothing is broken.
- Run
make build before pushing to catch TypeScript compilation errors (CI on the PR is informational for merge timing, not a substitute for local validation).
- Be thorough — read surrounding code context before making changes.
- Always update the action log after completing work — the wrap-up comment is generated from it.