---
name: merge-watch
description: Use when waiting for a PR to be mergeable — monitors CI, auto-fixes failures, and merges when green
---

# /merge-watch - Auto-Merge PR When Ready (with CI Fix) (Humand Tech Plugin)

Poll a pull request until it's mergeable, auto-fix CI failures, keep the branch up-to-date, and merge automatically.

## Plugin-first adaptation

```bash
eval "$(bash "${CLAUDE_SKILL_DIR}/../../scripts/workflow-setup.sh" <workspace_root>)"
```

- Use `$REPOS_FILE` for `lint_cmd`, `type_check_cmd`, and `pr_base`.
- Use `$LOCAL_CONFIG_FILE` for repo local paths.
- **Watcher script:** `bash "$PLUGIN_ROOT/scripts/merge-watch.sh"`.
- For local fixes on Node repos: `bash "$PLUGIN_ROOT/scripts/check-runtime.sh" <repo_path> -- bash -lc "<cmd>"`.

After running `workflow-setup.sh`, if `LOCAL_CONFIG_FILE` is empty, use **AskUserQuestion** before proceeding:

```
⚠️ No local-config.json found — I can't resolve repo paths without it.

[1] Run /initial-setup now (takes ~2 min, only needed once)
[2] Skip and continue anyway (some steps may fail)
```

If the user chooses **[1]**: invoke `/initial-setup` and stop this skill.  
If the user chooses **[2]**: continue, substituting `$LOCAL_CONFIG_FILE` references with best-effort guesses or skipping repo-dependent steps gracefully.

## Feature Resolution

See `docs/feature-resolution.md` for the full algorithm. Summary: use session context if clear; otherwise resolve from `git branch --show-current` in CWD + `git worktree list --porcelain` per repo in `$LOCAL_CONFIG_FILE`.

This skill accepts an optional branch argument (example: `/merge-watch feat/SQSH-1234-foo`) to override CWD detection.

## Superpowers-first hooks

- CI or runtime failures: enforce `systematic-debugging`.
- Before claiming merged/fixed: enforce `verification-before-completion`.

## Workflow

### 1. Determine Target PR

Resolve the PR to watch using this priority:

1. **User provides repo + PR number** → use directly.
2. **User provides only a PR number** → use the repo from the current working directory.
3. **User provides nothing** → detect repo and PR from the current branch (`gh pr view`).

Do **not** ask the user for merge method or poll interval. The merge method is determined by the repo's GitHub settings (only one method is enabled per repo). The poll interval is fixed at 20 seconds.

### 2. Resolve Local Repo Path

The agent needs a local checkout to fix CI failures. Resolve the repo path:

1. **Feature Resolution** — run the Feature Resolution algorithm above; use the path for the target repo.
2. **`$LOCAL_CONFIG_FILE`** — fall back to the repo mapping if feature resolution yields nothing.
3. **`REPO_ARG`** — if the user passed a local path, use it directly.

To find the PR number when not provided by the user, use:
```bash
gh pr list --repo <github> --author @me --state open --json number,headRefName,url \
  --jq '.[] | select(.headRefName == "<branch>")'
```

Read the repo's config from `$REPOS_FILE` for lint/type-check commands and other settings.

### 3. Prerequisite Check

Before starting the watcher, verify `jq` is available:

```bash
command -v jq
```

If missing, stop with a clear message:

```
merge-watch requires jq. Install it and run /merge-watch again.
```

### 4. Run the Watch-and-Fix Loop

Run the merge-watch script in the **foreground** with CI failure detection enabled:

```bash
EXIT_ON_CI_FAIL=1 bash "$PLUGIN_ROOT/scripts/merge-watch.sh" \
  "<repo_path>" "<pr_number>"
```

Use a **1-hour timeout** on the shell call (`timeout: 3600000`). The script polls every 20 seconds.

#### Exit code handling

| Exit Code | Meaning | Action |
|-----------|---------|--------|
| **0** | PR merged successfully | Report success, done |
| **1** | PR closed or fatal error | Report to user, done |
| **2** | CI failed (all checks complete, ≥1 failed) | Proceed to **Fix CI** (step 5) |
| **3** | Branch is behind and API update failed twice (likely merge conflicts) | Proceed to **Update Branch** (step 6) |
| **4** | Missing prerequisite (`jq`) | Report install guidance, stop |
| **timeout** | Still waiting after 1 hour | Stop current run and report timeout; optionally start background fallback (step 7) |

### 5. Fix CI

When the script exits with code 2, fix the failing checks.

#### 5a. Fetch Failure Details

Get the head SHA and failed run logs:

```bash
cd <repo_path>

# Current head SHA
HEAD="$(gh pr view <pr_number> --json headRefOid -q .headRefOid)"

# List workflow runs for the head commit
gh run list --commit "$HEAD" --json databaseId,conclusion,name \
  --jq '.[] | select(.conclusion == "failure")'

# Get logs from each failed run (last 300 lines per run to stay within limits)
gh run view <run_id> --log-failed 2>&1 | tail -300
```

If `gh run list` returns no results (non-GitHub-Actions CI), fall back to the check-runs API:

```bash
gh api "repos/<owner/repo>/commits/$HEAD/check-runs" \
  --jq '.check_runs[] | select(.conclusion == "failure") | {name, output: .output}'
```

#### 5b. Analyze Errors

Parse the CI output to categorise failures:

- **Lint errors** — file paths + rule violations
- **Type errors** — file paths + TS error codes + messages
- **Build errors** — compilation failures, missing imports
- **Test failures** — test names + assertion messages

#### 5c. Fix Locally

1. Make sure the local branch is up to date:

   ```bash
   cd <repo_path>
   git pull --ff-only origin <branch_name>
   ```

2. Read the failing files and fix the issues.
3. Run local verification using commands from `repos.json`:

   For npm/pnpm repos, prefer:

   ```bash
   bash "$PLUGIN_ROOT/scripts/check-runtime.sh" <repo_path> -- bash -lc "<lint_cmd>"
   bash "$PLUGIN_ROOT/scripts/check-runtime.sh" <repo_path> -- bash -lc "<type_check_cmd>"
   ```

   For non-node repos:

   ```bash
   <lint_cmd>        # e.g. pnpm biome:check
   <type_check_cmd>  # e.g. pnpm tsc
   ```

4. If local checks still fail, iterate on the fix before committing.

#### 5d. Commit and Push

```bash
cd <repo_path>
git add -A
git commit -m "[Fix] Fix CI errors (<TICKET>)"
git push
```

If there is no ticket in context, omit the ticket reference from the commit message.

#### 5e. Re-run the Watcher

Go back to **step 4**. The new push triggers a fresh CI run; the script will poll until it completes.

#### Fix Attempt Limits

Cap fix attempts at **3**. If CI still fails after 3 fix cycles:

1. Report the remaining errors to the user.
2. Fall back to **background mode** (step 7) so the PR auto-merges if the user fixes manually.

### 6. Update Branch

When the script exits with code 3, the branch is behind the base and the GitHub API couldn't update it (usually merge conflicts).

#### 6a. Fetch Base Branch

Read `pr_base` from the repo's entry in `repos.json` (e.g. `develop`, `main`).

```bash
cd <repo_path>
git fetch origin <base_branch>
git pull --ff-only origin <branch_name>
```

#### 6b. Rebase onto Base

```bash
git rebase origin/<base_branch>
```

If there are conflicts:

1. List the conflicting files (`git diff --name-only --diff-filter=U`).
2. Read each conflicting file and resolve the conflicts — prefer the feature branch changes when intent is clear, otherwise combine both sides.
3. After resolving each file: `git add <file>`.
4. Continue the rebase: `git rebase --continue`.
5. Repeat until the rebase completes.

If the rebase is too complex to resolve automatically, abort (`git rebase --abort`), report the conflicts to the user, and fall back to **background mode** (step 7).

#### 6c. Push

A rebase rewrites history, so a force push is required:

```bash
git push --force-with-lease
```

#### 6d. Re-run the Watcher

Go back to **step 4**. The push triggers a fresh CI run.

### 7. Background Fallback

When falling back to background mode (after fix limit, unresolvable conflicts, or 1-hour timeout), launch **without** `EXIT_ON_CI_FAIL` and cap runtime to one hour:

```bash
nohup timeout 3600 bash "$PLUGIN_ROOT/scripts/merge-watch.sh" \
  "<repo_path>" "<pr_number>" \
  > /tmp/merge-watch-<pr_number>.log 2>&1 &
echo "PID: $!"
disown
```

Check progress with `cat /tmp/merge-watch-<pr_number>.log`.
Stop manually with `kill <PID>`.

### 8. Confirmation

After launching or completing, report:

**If merged (exit 0):** Report success. Offer `/start` to begin a new feature if the user wants.

```
PR #<number> in <repo> has been merged!
```

**If CI was fixed and now watching:**

```
PR #<number> in <repo> — CI failed <N> time(s), fixed automatically.

Fix commits:
  - abc1234 [Fix] Fix CI errors (TICKET)
  - def5678 [Fix] Fix CI errors (TICKET)

Now watching for merge...
```

**If watching in background (fallback):**

```
Watching PR #<number> in <repo> for merge readiness.

  Interval: 20s
  Branch will be auto-updated if behind.
  Merge method: whatever the repo allows.

The watcher is running in the background (PID <pid>).
Check progress: cat /tmp/merge-watch-<pr_number>.log
Stop manually: kill <pid>
```

## Options

- `/merge-watch` — watch the PR for the current branch in the current repo
- `/merge-watch <pr_number>` — watch a specific PR in the current repo
- `/merge-watch <owner/repo> <pr_number>` — watch a PR in a different repo

## Error Handling

- If `gh` CLI is not authenticated: prompt user to run `gh auth login`.
- If `jq` is missing: stop with install guidance and do not start watcher.
- If no PR is found for the current branch: show error and suggest creating one with `/pr`.
- If merge is blocked by required reviews (not CI): the script keeps polling — inform the user reviews are needed.
- If the local branch can't be fast-forwarded: run `git pull --rebase` and retry.
- If fix attempts are exhausted: report errors clearly and fall back to background mode.

## Prerequisites

- `gh` CLI authenticated (`gh auth login`)
- `jq` installed (used to parse API responses in the polling loop)

## Notes

- The watch-and-fix loop keeps the agent engaged until the PR is merged or fix attempts are exhausted.
- Foreground watch is capped at 1 hour per run.
- Background mode (nohup + timeout) is only used as a fallback when fixing is not possible or limits are reached.
- Safe to cancel at any time (`kill <PID>` for background, or end the conversation).
- The `--match-head-commit` flag ensures the merge only happens on the exact SHA that was checked, preventing races.
- The `--delete-branch` flag cleans up the remote branch after merge.
- The script does **not** use `set -e`; `gh pr merge` returns non-zero when blocked, which would kill the loop prematurely.
