feat(cos): migrate branch reconciler to a per-app CoS scheduled task#2240
Merged
Conversation
The branch & PR reconciler (delete merged orphaned branches, open PRs for
pushed-but-unopened work, resolve conflicts, drive review, auto-merge when
green) was a PortOS-only feature with its own daily cron, /api/branch-reconcile
route, settings.branchReconcile toggle, and a dedicated Settings tab, hardwired
to the PortOS checkout.
It is now a first-class per-app CoS task type, `branch-reconcile`, configured
like every other improvement task (per-app enable, provider/model, cadence,
cooldown, budget). It runs `perpetual` (drain-until-done): each dispatch runs
the deterministic, peer-safe cleanup + classification against the app's repo,
dispatches the coordinator agent only while actionable in-flight branches
remain, then parks on a daily recheck — so no agent is burned on a no-op run.
- taskSchedule.js: new `branch-reconcile` type (perpetual, disabled default,
daily recheck, per-app action toggles), MANAGED_AGENT_OPTIONS lock, description
- cosTaskGenerator.js: deterministic per-app pre-step (mirrors pr-watcher/
reference-watch), injects {inFlightBranches}, parks/dispatches
- branchReconcile.js: coordinator prompt helpers moved here from the deleted
scheduler (filterActionable/desiredEndState/formatInFlightForPrompt), tested
- taskPromptDefaults: coordinator prompt + version + integrity snapshot
- cosValidation.js: cleanupMerged/openPr/resolveConflicts/autoMerge are now
sanitized per-app task-metadata booleans
Removes the bespoke scheduler, route, settings key, Settings tab, API helpers,
navManifest entry, and boot cron. Peer-safety (local refs only) is unchanged.
Claude-Session: https://claude.ai/code/session_01WQ6wsghzJAWWV778jAER9o
…a progress signature
The claude review flagged that making branch-reconcile `perpetual` with
metadata.perpetual=true removed the old daily-cron throttle, so a branch the
coordinator can't finish (a NEEDS_PR branch judged "not ready", or an IN_REVIEW
PR blocked on human approval / red CI) stayed actionable every tick and
re-dispatched an identical coordinator back-to-back forever.
Add a progress-signature convergence guard: dispatch only when the actionable
set changed since the last dispatch. A productive run advances branches through
states (changing the signature) so the drain keeps making progress; a run that
leaves the same branches in the same states parks on the recheck cadence instead
of looping.
- branchReconcile.js: actionableSignature() (order-independent {branch:state:pr})
- taskSchedule.js: getPerpetualSignature/setPerpetualSignature + parkPerpetual
stores the signature on a no-progress park
- cosTaskGenerator.js: signature compare → park on no-progress, dispatch + record
on change; document the transient-vs-persistent reconcile-failure trade-off
Claude-Session: https://claude.ai/code/session_01WQ6wsghzJAWWV778jAER9o
…hReconcile opt-ins Codex review found two issues in the migration: [P1] Signature stall for slow PRs. The progress signature (branch:state:pr#) did not change when a stuck IN_REVIEW PR later became mergeable (CI turned green, review cleared), so after the first no-progress park every daily recheck saw the same signature and re-parked — auto-merge never fired. Fix: the signature only suppresses the immediate back-to-back refill, never a recheck. On a no-progress park the stored signature is now CLEARED, so the next daily recheck re-dispatches unconditionally (re-driving the PR). Also add PR `mergeable` to the signature so a UNKNOWN→MERGEABLE transition within the same state counts as progress. [P2] No migration for existing opt-ins. Removing the old scheduler + defaulting the new task to disabled would silently stop the reconciler for anyone who had settings.branchReconcile.enabled: true. Add migration 170: carry the enabled reconciler into the new branch-reconcile task (cron→recheckCron, actions→ taskMetadata), scoped to the PortOS app only (disable on other managed apps to preserve the old PortOS-only scope), and drop the dead settings key. - branchReconcile.js: actionableSignature includes mergeable - cosTaskGenerator.js: clear signature on no-progress park (daily re-drive) - scripts/migrations/170-branch-reconcile-to-cos-task.js (+ test) Claude-Session: https://claude.ai/code/session_01WQ6wsghzJAWWV778jAER9o
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.
What
Migrates the PortOS-only Branch & PR Reconciler into a first-class per-app Chief-of-Staff scheduled task (
branch-reconcile), so it can finish unfinished local git work on any managed app — not just PortOS.The reconciler still does the same job: delete fully-merged orphaned local branches (+ their worktrees), open PRs for pushed-but-unopened work, resolve merge conflicts, drive the review loop, and auto-merge when a PR is green. It's just no longer a bespoke subsystem.
How
branch-reconciletask type (taskSchedule.js):perpetual(drain-until-done), disabled by default, daily recheck, per-app action toggles (cleanupMerged/openPr/resolveConflicts/autoMerge),useWorktree/openPRlocked off viaMANAGED_AGENT_OPTIONS(the coordinator runs in the app's live checkout to see sibling worktrees).cosTaskGenerator.js): runs the peer-safereconcile()on the app's repo (mirroring howpr-watcher/reference-watchdo programmatic work in the generator), injects{inFlightBranches}into a coordinator prompt, and dispatches only while actionable in-flight branches remain.branch:state:pr#:mergeable) suppresses the immediate back-to-back refill when a coordinator run makes no progress (aNEEDS_PRbranch judged "not ready", anIN_REVIEWPR blocked on human review / red CI), but never suppresses the daily recheck — so a PR that becomes green overnight still gets driven to merge.branchReconcile.js(the Tier-1 classifier + cleanup) is unchanged and alreadyrepoPath-parameterized; the coordinator prompt helpers moved here from the deleted scheduler, now unit-tested./api/branch-reconcileroute,settings.branchReconcilekey + Zod schema, the Settings tab, navManifest entry, and boot cron.branchReconcileopt-in into the new task (scoped to the PortOS app), so upgrading installs keep reconciling instead of silently stopping.Peer-safety (unchanged)
Only local
refs/heads/are ever touched. A branch created on a federated sync peer exists here only as a remote-tracking ref and is structurally invisible.Testing
actionableSignature, moved prompt helpers, perpetual signature persistence, and migration 170.claude+codex(per--review-withdefault); their findings (convergence loop, IN_REVIEW stall, missing migration) fixed in this branch.https://claude.ai/code/session_01WQ6wsghzJAWWV778jAER9o