feat: split fleet PR maintenance out of implement-issue into sweep-prs - #594
Merged
Conversation
Step 4 only pruned worktrees whose PR had merged. It never looked at an open PR, so it could not see either way a parked PR rots: red CI, or a branch that went CONFLICTING because other PRs merged into main ahead of it. A CONFLICTING PR creates no workflow run at all, so it presents as "CI never fired" and nobody investigates. Step 4 becomes 4a (fleet sweep) + 4b (worktree + branch). The sweep keeps the merged-prune arm unchanged and adds an open-PR arm that merges origin/main into stale branches, auto-resolves mechanical conflicts only, and reports everything else. A skip gate runs before both arms so the sweep never touches a worktree another agent owns — any live session at that cwd, uncommitted tracked changes, or a HEAD under 30 minutes old. The same section is what to run under /loop for continuous fleet maintenance, rather than forking a second skill that drifts. Step 9 gains the matching pre-push `git merge origin/main`: Step 4b cuts the branch from a current origin/main, but Steps 5-8 take hours and other PRs merge during them. Two behaviours found by dry-running the sweep against the real fleet: GitHub computes `mergeable` lazily, so the first query on a cold PR returns UNKNOWN and only triggers the computation — a single pass reports UNKNOWN for precisely the stale PRs the sweep exists to find, hence the retry. And 11 worktrees hold branches with local commits and no PR; those are reported, never deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYV1WdkadzkeUUgULhC3Dk
implement-issue owns exactly one PR — the issue it was invoked for — and had no idea whether that PR ever went green. It stopped at draft-PR-open, so a PR that failed the CI matrix (which quality-check.sh does not reproduce) or went CONFLICTING minutes later sat there unreviewed. Step 9 now merges origin/main before pushing: Step 4 cuts the branch from a current origin/main, but Steps 5-8 take hours and other PRs merge during them. Opening an already-CONFLICTING PR is worse than it sounds, because GitHub creates no workflow run at all for one — it presents as "CI never fired" rather than as a conflict. New Step 10 watches that PR to green via `gh pr checks --watch`, fixing failures in the worktree, and is explicitly scoped to this PR alone. Step 4 is unchanged: it still prunes merged worktrees only. Fleet-wide maintenance moves to a new sweep-prs skill, which walks every worktree, prunes merged ones, merges main into stale branches, and reports red CI. It has the piece implement-issue must not grow: an ownership skip gate. A live session at that cwd (any status — idle and blocked included), uncommitted tracked changes, or a HEAD under 30 minutes old means hands off, because merging under a running implement-issue moves its HEAD and puts two sessions on one branch. Two behaviours found by dry-running the sweep against the real fleet: GitHub computes `mergeable` lazily, so the first query on a cold PR returns UNKNOWN and only triggers the computation — a single pass reports UNKNOWN for precisely the stale PRs the sweep exists to find, hence the retry. And 11 worktrees hold branches with local commits and no PR; those are reported, never deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYV1WdkadzkeUUgULhC3Dk
The sweep acted on everything that survived the skip gate, in one pass, with no way to choose. Merging and pushing to a PR the user has not looked at should not be what happens because they forgot to pass a flag. Three modes: bare `/sweep-prs` classifies the fleet and reports what it would do, changing nothing; `/sweep-prs 437 579` acts on the listed PRs only; `/sweep-prs --all` acts on everything eligible and is the one to pair with /loop. The safe mode is now the default. Pruning merged worktrees stays automatic in every mode. It deletes only work already merged into main, so nothing is at risk, and gating it behind a flag recreates the failure it was added to fix -- a cleanup nobody chooses to run doesn't run, which is how 39 worktrees accumulated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYV1WdkadzkeUUgULhC3Dk
Step 10 told you to run `gh pr checks --watch` and read the result. On this skill's own PR that command returned "no checks reported on the 'feat/sweep-prs-skill' branch" about 8 seconds after the push -- the run existed and was in_progress, but --watch returned before it registered. That string has two causes that need opposite responses. A CONFLICTING PR has no run and never will, because GitHub does not build one; the fix is to merge origin/main. A just-pushed PR has a run that hasn't appeared yet; the fix is to wait on the run id. Reading either as green is how a red PR gets handed over as finished. Step 10 now requires telling them apart via `gh pr view --json mergeable` plus `gh run list --branch`, then `gh run watch <id> --exit-status`. Same correction in sweep-prs' rationalization table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYV1WdkadzkeUUgULhC3Dk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
implement-issuenow watches its own PR to green, and mergesorigin/mainbefore pushing.sweep-prsskill, runnable under/loop.implement-issueStep 4 is unchanged — it still prunes merged worktrees and nothing more.Problem
implement-issuestopped at draft-PR-open. It never checked whether that PR went green, and localquality-check.shdoes not reproduce the CI matrix. Separately, a PR that was green when its session ended goesCONFLICTINGas soon as other PRs merge ahead of it — and GitHub creates no workflow run at all for a conflicted PR, so it presents as "CI never fired" rather than as a conflict. Nobody investigates a PR that appears to have no checks.A dry run against the current fleet found #437 and #579 sitting
CONFLICTING DIRTY, both invisible for this reason.Changes
implement-issue(its own PR only)git fetch origin && git merge origin/mainbefore pushing. Step 4 cuts the branch from a currentorigin/main, but Steps 5–8 take hours and other PRs merge during them.gh pr checks --watch --fail-fast, fix failures in the worktree, re-check mergeability. Scoped explicitly to this PR — widening a single-issue session into fleet cleanup is how two sessions end up pushing to one branch.--watchblocks rather than polls, so it costs one wait, not a context re-read every 60s.sweep-prs(new)Skip gate → classify → act → report, over every worktree.
The skip gate is the piece
implement-issuemust not grow: a live Claude session at that cwd (any status —idleandblockedincluded, since an agent parked between Step 6 and Step 8 reads as idle and still holds its branch), uncommitted tracked changes, a HEAD under 30 minutes old, or detached/locked all mean hands off. Ownership is read withclaude agents --jsonrun unscoped, because sibling worktrees never appear in the project-scoped view.Then: merged → prune; open and
CONFLICTING/BEHIND→ mergeorigin/main, auto-resolve mechanical conflicts only (CHANGELOG## [Unreleased], imports, lockfiles),quality-check.sh, push; checks failing → auto-fix only Black/Ruff/missing-changelog, report real test failures; branch with no PR → report, never delete.Two behaviours found by dry-running the sweep
Both would have shipped silently:
mergeablelazily. The first query on a cold PR returnsUNKNOWNand triggers the computation. A single pass therefore reportsUNKNOWNfor precisely the stale PRs the sweep exists to find — it would have found nothing. Now retries 3× with a 3s backoff; verified all three PRs resolve on retry.Test plan
./scripts/quality-check.shpasses locallyCONFLICTING, fix: price the discharge gate off the cell the battery is actually in #579CONFLICTING, Consolidate PredictionSnapshotStore into the unified per-day DailyView format #490MERGEABLE CLEAN), 11 no-PR branches, and skipped 6 live-session worktreesUNKNOWNretry verified against feat: accept an external consumption forecast as a time-series entity #437/Consolidate PredictionSnapshotStore into the unified per-day DailyView format #490/fix: price the discharge gate off the cell the battery is actually in #579 — all resolve on the second queryNotes
Docs-only change — no
CHANGELOG.mdentry, since.claude/skills/is agent tooling with no user-visible effect. Neitherdocs/agents/bess-knowledge.mdnordocs/SOFTWARE_DESIGN.mddescribes anything this touches.No
Closes #— this isn't tracked by an issue.