From ac3231bfe478764ad16cce0ee5332d4c334d0abb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 12 Aug 2026 19:05:24 +0000 Subject: [PATCH] docs(source-control): merge-forward beats rebase for stale PR branches; statusCheckRollup running-check pitfall Co-authored-by: Kyle Sexton --- .../source-control/.claude-plugin/plugin.json | 2 +- plugins/source-control/CHANGELOG.md | 20 +++++++++++++++++++ .../skills/pull-request/reference/monitor.md | 18 +++++++++++++++-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/plugins/source-control/.claude-plugin/plugin.json b/plugins/source-control/.claude-plugin/plugin.json index 7f85f8d33..2623e3b53 100644 --- a/plugins/source-control/.claude-plugin/plugin.json +++ b/plugins/source-control/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "source-control", - "version": "0.53.11", + "version": "0.53.14", "description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-authored-by trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop \u2014 safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and standing merge-rung raises binding from the team-tracked layer only \u2014 with one named exception, where an invocation line explicitly typing both the autopilot tier keyword and the dedicated raise argument --merge c3-this-run widens that single invocation's merge authority up to C3 behind a fresh independent frontier-tier resolver, while C4-structural and C5-untrusted-provenance stay unconditionally human-merge), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply \u2014 interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep \u2014 never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.", "author": { "name": "Melodic Software", diff --git a/plugins/source-control/CHANGELOG.md b/plugins/source-control/CHANGELOG.md index 187baabaf..13866940b 100644 --- a/plugins/source-control/CHANGELOG.md +++ b/plugins/source-control/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to the `source-control` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.53.14] + +### Changed + +- **Stale-branch recovery defaults to merge-forward, not rebase + force-push (#1436).** `monitor.md`'s + conflict and stale-branch paths prescribed "force-push with lease", which auto-mode permission + classifiers commonly deny — the observed cost was a fresh branch + fresh PR per rebase, with every + review thread re-opened. Merging the default branch *into* the PR branch pushes fast-forward with + no force-push, and under a squash-only default branch the merge commits collapse on merge, so + linear-history requirements stay satisfied. Rebase remains the exception for projects that require + a linear PR branch and where force-push is actually available. + +### Added + +- **`statusCheckRollup` running-check pitfall documented (#1436).** An unfinished check reports + `conclusion: ""` (empty string), not `null`, so complement-shaped failure filters + (`conclusion != null and != "SUCCESS"`) count still-running checks as failures. `monitor.md`'s + multi-PR scan now carries the correct value-positive jq selectors for "failed" and "still + running". + ## [0.53.11] ### Fixed diff --git a/plugins/source-control/skills/pull-request/reference/monitor.md b/plugins/source-control/skills/pull-request/reference/monitor.md index e613ea9b4..6fccf4a89 100644 --- a/plugins/source-control/skills/pull-request/reference/monitor.md +++ b/plugins/source-control/skills/pull-request/reference/monitor.md @@ -178,6 +178,18 @@ Other open PRs: #103 fix/null-check — all checks green, awaiting review ``` +**`statusCheckRollup` pitfall: a running check has `conclusion: ""` (empty string), not `null`.** +The obvious "did anything fail" filter — `select(.conclusion != null and .conclusion != "SUCCESS")` +— therefore matches every **in-progress** check and reports still-running CI as failures. Select on +the values you mean, never on the complement: + +```bash +# failures only +--jq '[.statusCheckRollup[] | select(.conclusion=="FAILURE" or .conclusion=="TIMED_OUT" or .conclusion=="CANCELLED")]' +# still running +--jq '[.statusCheckRollup[] | select(.status!="COMPLETED")]' +``` + **Constraint: Monitor watches are branch-locked.** Monitor MUST run in the session that owns the branch (§3.5). Scanning is READ-ONLY — you cannot arm a Monitor watch for a PR on a different branch from this worktree. Report status and suggest: *"Switch to the worktree for `` to monitor PR #N."* **When NO other open PRs found:** report `No other open PRs need attention.` and let the session idle. @@ -190,7 +202,7 @@ Other open PRs: After each push, run this loop until convergence (**every** check in a terminal state + all comments addressed): -1. **Mergeable pre-check (MANDATORY before polling)** — `gh pr view --json mergeable,mergeStateStatus` FIRST. If `mergeable == "CONFLICTING"`, GitHub will NOT trigger workflows — integrate the default branch, resolve conflicts, force-push with lease, and restart the loop. Only proceed to CI polling when `mergeable == "MERGEABLE"`. **Never blame the platform for missing CI runs before checking this.** +1. **Mergeable pre-check (MANDATORY before polling)** — `gh pr view --json mergeable,mergeStateStatus` FIRST. If `mergeable == "CONFLICTING"`, GitHub will NOT trigger workflows — integrate the default branch (merge-forward first, per the stale-branch recovery rule in §3.2), resolve conflicts, push, and restart the loop. Only proceed to CI polling when `mergeable == "MERGEABLE"`. **Never blame the platform for missing CI runs before checking this.** 2. **Poll CI** — `gh pr checks ` every 30s (the standard monitor cadence), max 15 minutes per cycle. **Wait for ALL checks to reach a terminal state** (pass/fail/skipped) before suggesting merge — no exceptions, regardless of PR type. Never merge while any check is still pending or in_progress 3. **Check for new comments** — on each poll, also fetch new review comments (`gh api --paginate "repos///pulls//comments?per_page=100"`) 4. **Process comments immediately** — if a bot comments while CI is still running, start evaluating/researching that comment now. Don't wait for CI @@ -276,7 +288,9 @@ For each security finding: 5. **Implement** (only after 1-4) — make the change, re-run the project's build/test/lint gate, commit, push 6. **Loop restarts** — new push triggers 3.1 again. Track iteration count -**Stale branch recovery** — if CI fails because the branch is out of date with the default branch (merge conflicts, "branch is not up to date" errors, or tests failing due to default-branch-only changes): integrate (merge or rebase per the project's convention and the branch's own history), resolve conflicts conservatively, force-push with lease, restart the monitor loop from 3.1. Distinct from code failures — no research gate for the integration itself, only for conflicts requiring intent judgment. +**Stale branch recovery** — if CI fails because the branch is out of date with the default branch (merge conflicts, "branch is not up to date" errors, or tests failing due to default-branch-only changes): integrate, resolve conflicts conservatively, push, restart the monitor loop from 3.1. Distinct from code failures — no research gate for the integration itself, only for conflicts requiring intent judgment. + +**Merge-forward is the default integration; rebase is the exception.** `git merge /` *into* the PR branch resolves staleness and pushes **fast-forward** — no force-push, no history rewrite. A rebase rewrites the branch and demands `git push --force-with-lease`, which permission classifiers commonly deny in autonomous/auto-mode sessions; the observed failure shape is a lane concluding every rebase costs a fresh branch + fresh PR + closing the old one (re-opening every review thread from scratch), when the stale branch never needed a rebase at all. Under a squash-only default branch, the merge commits inside the PR branch collapse to one commit on merge and linear-history requirements stay satisfied — repeated merge-forwards as the default branch moves cost nothing. Rebase only when the project's convention requires a linear PR branch *and* force-push is actually available. **Escalation guard** — after **3 fix iterations**, STOP. Present a history table. The root cause may be environmental.