|
| 1 | +--- |
| 2 | +name: post-push-loop |
| 3 | +description: "Use after pushing a PR branch to autonomously monitor CI, fetch remote review findings, generate fixes, and iterate until CI is clean or a hard decision is needed. Invoke as: /post-push-loop [pr-number]" |
| 4 | +--- |
| 5 | + |
| 6 | +# Post-Push Loop |
| 7 | + |
| 8 | +Autonomously iterate through the CI/review cycle after a push. Loop exits only when |
| 9 | +external signals clear (CI green + no unresolved bot findings), or when a finding |
| 10 | +requires a human decision. |
| 11 | + |
| 12 | +## Invoke |
| 13 | + |
| 14 | +``` |
| 15 | +/post-push-loop [pr-number] |
| 16 | +``` |
| 17 | + |
| 18 | +If `pr-number` is omitted, auto-detect from `gh pr view --json number -q .number`. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Dependencies |
| 23 | + |
| 24 | +This skill requires `~/.claude/scripts/post-push-status.sh` (from the `claude-config` |
| 25 | +repo). Before starting, verify it exists and is executable. If missing, escalate |
| 26 | +immediately — do not attempt to create or substitute it. |
| 27 | + |
| 28 | +Additional requirements: `gh` (authenticated), `jq`, `python3`, and a `pre-push` git |
| 29 | +hook with `POSTPUSH_LOOP` bypass support. |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## Protocol |
| 34 | + |
| 35 | +### Before Starting |
| 36 | + |
| 37 | +1. Verify `~/.claude/scripts/post-push-status.sh` exists and is executable |
| 38 | +2. Confirm current branch: `git branch --show-current` — must NOT be `main` |
| 39 | +3. Confirm PR number: `gh pr view --json number -q .number` |
| 40 | +4. Confirm `POSTPUSH_LOOP` will be set for all git push calls in this session |
| 41 | + |
| 42 | +### The Loop |
| 43 | + |
| 44 | +Repeat until **Exit: Success** or **Exit: Escalate**: |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +#### Phase 1 — WAIT for CI to complete |
| 49 | + |
| 50 | +Poll every 30 seconds: |
| 51 | + |
| 52 | +```bash |
| 53 | +bash ~/.claude/scripts/post-push-status.sh <PR_NUMBER> |
| 54 | +``` |
| 55 | + |
| 56 | +Continue polling while `CI_STATE=PENDING`, `CI_STATE=EXPECTED`, or `CI_STATE=UNKNOWN`. |
| 57 | + |
| 58 | +`CI_STATE=UNKNOWN` indicates a transient error (network failure or no CI checks registered yet) — treat it as pending and continue polling. It counts against the 15-minute timeout. |
| 59 | + |
| 60 | +Timeout: if `CI_STATE` has not resolved after 15 minutes, escalate with reason |
| 61 | +"CI did not complete within timeout — check for stuck jobs." |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +#### Phase 2 — EVALUATE termination |
| 66 | + |
| 67 | +Parse script output using this exact decision table — evaluate in order: |
| 68 | + |
| 69 | +| CI_STATE | FINDING lines present? | Action | |
| 70 | +|---|---|---| |
| 71 | +| `SUCCESS` | No | **Exit: Success** | |
| 72 | +| `SUCCESS` | Yes | Proceed to Phase 3 | |
| 73 | +| `FAILURE` | Either | Proceed to Phase 3 | |
| 74 | +| `ERROR` | Either | Proceed to Phase 3 | |
| 75 | +| `UNKNOWN` | Either | Return to Phase 1 (transient — treat as pending) | |
| 76 | + |
| 77 | +`CI_STATE=SUCCESS` alone is NOT sufficient to exit — zero `FINDING` lines are also required. |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +#### Phase 3 — CLASSIFY findings |
| 82 | + |
| 83 | +`FINDING` lines have the format: |
| 84 | + |
| 85 | +``` |
| 86 | +FINDING source=<bot> file="<path>" line=<line> comment=<text> |
| 87 | +``` |
| 88 | + |
| 89 | +The `file=` value is always double-quoted (strip the outer quotes when reading it). This handles file paths that contain spaces. |
| 90 | + |
| 91 | +**Note on `issues/comments` staleness:** Findings from the PR conversation thread |
| 92 | +(`issues/comments`) cannot be filtered by commit — that API provides no |
| 93 | +`original_commit_id` field. These findings may include bot comments from previous |
| 94 | +commits. Treat an `issues/comments` finding with an empty `file=` and `line=` as a |
| 95 | +potential staleness indicator; apply extra scrutiny before classifying as CONFIDENT_FIX. |
| 96 | + |
| 97 | +For each `FINDING` line, classify as **CONFIDENT_FIX** or **ESCALATE**: |
| 98 | + |
| 99 | +**CONFIDENT_FIX** (all of the following must be true): |
| 100 | + |
| 101 | +- Finding has a specific `file=` and `line=` reference |
| 102 | +- Fix is local to that file — explicitly verify before classifying: (1) no other files import or call the changed symbol, (2) no tests outside this file reference it, (3) the change does not affect a shared interface or type |
| 103 | +- Finding falls into a known pattern: |
| 104 | + - Lint or style error |
| 105 | + - Type error or missing type annotation |
| 106 | + - Missing null/undefined check |
| 107 | + - Unused import or variable |
| 108 | + - Test assertion mismatch with clear expected value |
| 109 | + |
| 110 | +**ESCALATE** (any of the following): |
| 111 | + |
| 112 | +- Finding is architectural ("this approach should be reconsidered") |
| 113 | +- Finding touches a security-critical path: |
| 114 | + - Files matching: `**/auth/**`, `**/jwt/**`, `**/password/**`, `**/session/**`, |
| 115 | + `**/payment/**`, `**/billing/**`, `**/db/**`, `**/migrations/**`, |
| 116 | + `**/crypto/**`, `**/secrets/**` |
| 117 | +- Finding references behavior not introduced by this branch |
| 118 | +- Root cause is ambiguous (no specific file/line, or conflicting signals) |
| 119 | +- Two findings appear to conflict with each other |
| 120 | +- `CI_STATE=ERROR` (infrastructure failure, not code failure) |
| 121 | + |
| 122 | +**If ANY finding is ESCALATE**: stop. Do NOT apply any fixes. Go to **Exit: Escalate**. |
| 123 | + |
| 124 | +**If ALL findings are CONFIDENT_FIX**: proceed to Phase 4. |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +#### Phase 4 — FIX |
| 129 | + |
| 130 | +Apply edits for each confident finding. Keep changes minimal — fix exactly what the |
| 131 | +finding describes, nothing more. |
| 132 | + |
| 133 | +After all edits: |
| 134 | + |
| 135 | +```bash |
| 136 | +git diff HEAD |
| 137 | +``` |
| 138 | + |
| 139 | +Review the diff briefly. If the diff is larger than expected or touches files not |
| 140 | +referenced in findings, treat as ESCALATE. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +#### Phase 5 — COMMIT |
| 145 | + |
| 146 | +```bash |
| 147 | +git add <only the files you changed> |
| 148 | +git commit -m "fix: address <summary of findings> |
| 149 | +
|
| 150 | +Post-push loop iteration N: <list of findings addressed>" |
| 151 | +``` |
| 152 | + |
| 153 | +Note: the pre-commit hook will run automatically. If it blocks the commit, treat as |
| 154 | +**Exit: Escalate** — surface both the hook finding and the remote finding. |
| 155 | + |
| 156 | +Environment for push: set `POSTPUSH_LOOP=1` to bypass the Protocol 4 interactive prompt. |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +#### Phase 6 — PUSH |
| 161 | + |
| 162 | +```bash |
| 163 | +POSTPUSH_LOOP=1 git push |
| 164 | +``` |
| 165 | + |
| 166 | +Return to Phase 1. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +### Exit: Success |
| 171 | + |
| 172 | +``` |
| 173 | +✅ POST-PUSH LOOP COMPLETE |
| 174 | +
|
| 175 | +PR #NNN | N iterations |
| 176 | +CI state: SUCCESS |
| 177 | +Unresolved bot findings: 0 |
| 178 | +
|
| 179 | +Commits pushed: |
| 180 | + <list of commit hashes + messages> |
| 181 | +``` |
| 182 | + |
| 183 | +### Exit: Escalate |
| 184 | + |
| 185 | +``` |
| 186 | +⚠️ LOOP PAUSED — Human decision required |
| 187 | +
|
| 188 | +PR #NNN | Iteration N | CI: <state> |
| 189 | +
|
| 190 | +FINDING(S) requiring your input: |
| 191 | + [For each finding] |
| 192 | + Source: <bot> |
| 193 | + File: <path>:<line> |
| 194 | + Comment: <text> |
| 195 | + Reason escalated: <reason> |
| 196 | +
|
| 197 | +CONTEXT: |
| 198 | + Fixes applied this session: <list or "none"> |
| 199 | + Local review at last commit: passed / blocked |
| 200 | + Current CI state: <state> |
| 201 | +
|
| 202 | +OPTIONS: |
| 203 | + 1. Provide direction here → I resume with your guidance |
| 204 | + 2. Take over manually → type "take over" to exit loop |
| 205 | + 3. Abandon → type "abandon" to exit, leave branch as-is |
| 206 | +``` |
| 207 | + |
| 208 | +Await human response. Do not proceed autonomously. |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +## Hard Constraints |
| 213 | + |
| 214 | +- Never exit with "clean" status based on your own assertion — only based on |
| 215 | + `CI_STATE=SUCCESS` AND no `FINDING` lines from the status script. |
| 216 | +- Never apply fixes when any finding is classified ESCALATE. |
| 217 | +- Never use `--no-verify` on any git command. |
| 218 | +- Never push to `main`. |
| 219 | +- Set `POSTPUSH_LOOP=1` on every `git push` call within this skill. |
| 220 | +- Never exceed 5 push iterations in a single loop session. After 5 iterations, |
| 221 | + escalate with reason "Iteration limit reached — autonomous loop terminated to |
| 222 | + prevent unbounded cost." |
0 commit comments