Keep all CI watching inside the fork, and even there make it event-driven (see below). A foreground gh pr checks --watch / gh run watch in the main thread holds a Bash slot up to the 600s timeout and freezes the session, so route it through a harness-tracked background job instead.
You own PR # in <owner/repo> from now until it is merged or you are genuinely stuck. Loop:
- Watch CI fail-fast, silently. Arm exactly ONE event-driven watch and let it be the only thing that wakes you. It must complete only on a terminal or changed state — a check result (any fail / all pass), a new head sha, a merge or close event, or your own deadline — never on a timer tick that re-reports "pending". Run
gh pr checks <N> --watch --fail-fast --interval 20 with run_in_background: true (never a foreground Bash slot) so it returns the instant ANY check fails or all pass. --fail-fast needs gh ≥ 2.42; if it is rejected, drop the flag and run a background until loop over gh pr checks <N> --json name,state,bucket that exits only when the aggregate state differs from when you armed it.
- If all checks pass → merge. Enable auto-merge:
gh pr merge <N> --auto --squash. When the PR is already green and mergeStateStatus is CLEAN this merges immediately, so there is never a reason to escalate. Never pass --admin or --force, even when everything is green: the house rules forbid them and the permission policy denies them. Then report "merged" with the squash commit sha and stop.
- If a check failed → diagnose and fix.
- Pull the failing logs:
gh pr checks <N> to find the failed check, then gh run view <run-id> --log-failed (or --log for the failing job) to read the actual error. Read the real error, do not guess.
- Work on the PR branch in an isolated worktree off the PR head, never the shared main checkout (in index/ix a PreToolUse hook blocks edits on main anyway).
git fetch origin then git worktree add ../<repo>-pr<N> <headRef> and fix there. Validate the fix at the layer it touches via the repo's own commands (e.g. nix build .#…, nix run .#lint, the scoped nix build .#checks.<system>.<name>) before pushing, so you are not burning CI to test a guess.
- Commit (match the repo's commit style) and
git push. Remove the worktree when done.
- Re-watch from step 1.
Stuck detection (do not loop forever). Keep a short signature of each failure (check name + the load-bearing error line). If the SAME failure recurs after your fix, or you see 3 consecutive failed CI rounds without net progress, STOP and report: the failing check, the error, what you tried, and why you could not fix it. A flaky/transient infra failure (timeout, runner died, cache outage) is not your bug: re-run it once (gh run rerun <run-id> --failed) and only escalate if it fails again the same way.
Escalate, do not guess, when: the fix needs a product decision, the failure is in code you did not touch and the right fix is ambiguous, a required check needs human approval, or merging is blocked by a review the user must give. Report the specific blocker.
Watch the clock. If a single CI round runs far longer than that repo's checks normally take, investigate the long-pole step rather than waiting it out (in index/ix, treat any check over ~1 min as a problem to look at, not wait on).
Report only on state change. Every turn-end message you write lands as a task notification in the parent session, so a "still pending, waiting" update is pure noise. Never emit a no-change status report; if you wake and nothing changed, re-arm and yield silently. Batch progress into state-change reports only (a check failed, you pushed a fix, merged, stuck, blocked). Yielding silently never means stopping: keep a live watch armed whenever the outcome is pending, and re-arm it BEFORE ending the turn.
Report concisely when done: final state (merged + sha / stopped-stuck / blocked), and for anything you fixed, one line per fix.