---
title: Dev Pipeline Maintenance
---

# Dev Pipeline Maintenance

## What it is

**Dev Pipeline Maintenance** is two housekeeping skills that ride with the Dev Pipeline and keep your repos tidy without you lifting a finger:

- **worktree-cleanup** — reaps the git worktrees left behind by finished work. It removes a worktree only when its branch's work is **provably landed** in the integration branch (`main`, else `master`), judged by **content** (patch-id) so it also catches rebase / replay landings, not just direct ancestors — with GitHub PR status as an extra signal when available, never required. **A squash-landed branch is the exception:** squashing rewrites patch-ids, so catching one takes an extra check that needs judgment and is deliberately interactive-only. The unattended daily run below therefore *keeps* a squash-landed worktree rather than reaping it — run `/worktree-cleanup` yourself to clear those. It never removes a locked, dirty, active, or unmerged worktree, and never the main checkout, and **every delete is reversible** (it records a recovery SHA before deleting a branch, and saves a patch of any uncommitted work first). On request it can also **archive** idle unmerged worktrees (reclaim disk, keep the branch) and clean up stale local branch refs.
- **merge-all-ready** — lands every local branch marked **ready-to-merge** onto the integration branch, one at a time, **atomically** and **local-only** (it never pushes and never touches `origin`). A clean or fast-forward merge lands automatically; a conflict is resolved only when it can be done safely and verifiably, otherwise the branch is skipped and reported. It never force-merges and never loses committed work.

### When they install

Both skills install into your `~/.claude/skills/` the moment you turn **ON** the Dev Pipeline (they are gated on the same `devPipelinePanelEnabled` setting), and are removed if you turn it off. This requires Omniscio's global skill-sync, which is on by default. Once installed, `/worktree-cleanup` and `/merge-all-ready` work in every project.

## Where to find it

There is nothing to open for the daily run — it happens on its own, on the schedule described below. Both jobs are also ordinary skills you can run by hand, which is how you clear the cases the unattended pass deliberately leaves alone. The off switch is under **How it behaves**.

## How it behaves

### The daily automatic run

As **part of the Dev Pipeline**, Omniscio runs these two skills automatically once a day. Each day the in-process maintenance job:

- finds the hubs that had **development work that day** (a dev-pipeline run), that participate in the pipeline (per the Dev Pipeline panel's "Which repos"), **and that actually hold a git repository** — a hub is where a session was *created*, not necessarily the repo its work sat in, so a notes hub, a data folder or an agent home is never a maintenance target and never receives one of these sessions,
- and, for each one that has not already been maintained today, spawns **one background session** that runs worktree-cleanup in that repo and then *reports* on the landing queue.

> **The daily run does not land anything — it never has since 2026-09-09.** It reads the auto-lander's queue and status and tells you what is waiting and what is stuck. The auto-lander is the one thing that moves your integration branch. This changed after a daily run swept alongside the armed lander and landed a branch its author had deliberately marked on hold: two movers on one branch is a defect, not a safety net.

It is **on by default** whenever the Dev Pipeline is enabled, with an off switch in the **Dev Pipeline panel → Setup tab** (`devPipelineDailyMaintenanceEnabled`). Cost is bounded: at most one session per repo per day, the run honors your **daily spend cap**, and a per-day repo limit caps a busy day. By default it runs on a **lighter, lower-cost model** (Claude Sonnet) to keep it cheap — but right below the toggle you can **choose the engine, model, and thinking level** it uses. The picker offers the Claude-family engines (native Claude plus the Claude-compatible vendors), since the run uses the Claude Code housekeeping skills; leave it on "default" to keep the cheap Sonnet. The daily job also can't re-trigger off its own runs, **by construction**: maintenance sessions run the two *skills*, not the pipeline, so they never record a dev-pipeline run — and a dev-pipeline run is the only thing the "had development work today" signal looks at. (The sessions do carry a distinct tag, but that is provenance for you, not the guard.)

**Every stranded branch and working folder is reviewed, by name.** After the cleanup, the run
accounts for everything it could not clear: working folders a safety rule kept (with the rule that
kept each one), dev-pipeline runs that stopped part-way, and branches the auto-lander set aside after
its rescue attempts ran out. Anything you could pick up gets a **one-click link that opens a new
session for it as an unsent draft** — the brief is filled in, and nothing starts or costs anything
until you read it and press send. The run never starts those sessions itself. If it could not read
one of those lists, it says so rather than reporting "none".

**No alerts — it just talks to you.** The run never raises inbox alerts or cards of its own; its
report is the one place it tells you anything.

**It only writes to you when something needs you.** A run that went fine — whether it found nothing to do or reaped four worktrees and flagged three branches waiting to land — **quietly archives itself**, so your inbox stays clean. The one thing that brings it to your inbox is a **Needs a look** item: a branch it had to skip, a conflict it could not resolve safely, a step that errored, or anything else that wants your decision. Then it reports back in a short, scannable summary. Nothing is lost when it archives — every run stays readable in your **Archive**, so you can always go see what it did.

### Old branches: cleared automatically, or left alone

Worktrees are only half the mess. A branch outlives its working folder, and they pile up — measured
on the author's own machine, **1,011 local branches against 262 worktrees**, growing ~79 a day.

**If a branch is provably finished, it is deleted for you.** No card, no question.

**If it cannot be proven finished, it is left alone — nobody asks you.** When work lands by being
*copied* onto the main branch (a cherry-pick or a rebase) the copy gets a new identity, so neither of
git's two "did this already land?" checks can see it. Sampled across ten branches here, **both
checks failed on all ten.** There is no automatic proof to wait for, so only a person could decide.
Omniscio used to ask once a day with approval cards; since 2026-09-26 those cards are **off by
default**, because on the author's machine 29 of the first 32 answered were a "no". Nothing is
deleted, and the daily report still counts how many such branches there are.

**Want the cards back?** Start Omniscio with `AMC_ENABLE_STALE_BRANCH_RETIRE_ASK=1` set. Once a day
you may then get **two approval cards**, deliberately separate so you can say yes to one and no to
the other:

- **Branches that look finished** — the main branch has already changed every file each of them
  touched, which almost always means the work landed under a different name. The card says *strong
  evidence, not proof*, because that check cannot see a file both sides edited.
- **Branches that still hold work** — the main branch never received some of their files, and the
  card **shows you the counts**. The measured spread was 1, 1, 9, 925 and 33,960 files, which is
  exactly why these never hide inside the first group.

**The daily report reviews a waiting card for you.** While a card is waiting, the report lists every
branch on it — oldest first, grouped by verdict — as *likely replaced by later work (not proven)* or
*still holds unique work*, with its file count and age, and links straight to the card. You approve a
list you have actually read, not a count. A card you declined is your answer, and the report never
brings it up again. The card is decided on its own: the inbox's **Approve all** and **Reject all**
(and the selection actions) skip it and leave it waiting, so one sweep of the Approvals pile can
never delete branches.

**Nothing is deleted without a backup that never expires.** Every branch tip is archived first and
the archive is re-read and confirmed before the branch goes; a branch that picked up new work since
the card was made is skipped, not deleted. Declining a batch quietly parks it for a week rather than
asking again tomorrow. Nothing here can auto-approve itself — no setting, no toggle, no agent.

**A branch that something is still working in is kept too**, and the reason is written down where
you can find it. That covers a branch whose session is still running, one you pinned to keep, one
carrying a hold, and — the case that used to slip through — one sitting in the middle of a rebase.
A folder part-way through a rebase looks *empty* to the check that asks "is any working folder using
this?", which is why a rebase in progress is now asked about separately rather than inferred.

The daily report also prints one number worth watching: **branches per worktree**. A branch should
not outlive its working folder, so if that number is drifting up, the pile is growing.

**"Background cleanup is slow on some old working folders."** This card is about working folders,
not about landing. When three or more of your working folders sit on branches that have fallen far
behind the main branch (10,000 commits or more), Omniscio times one real background check on the
furthest-behind of them, at most every six hours and never in the first ten minutes after the app
starts. The card appears only if that check was slow — 15 seconds or more — and it says how long the
check took and names the furthest-behind branches. It never asks you to delete anything: bringing a
branch you still want up to date makes its checks fast again, and the card clears itself once the
checks are fast.

### What the daily verifier checks

Before it considers spending anything, the daily run does a **free, automatic check** that the whole
landing-and-cleanup system actually worked. It reads what the app already knows — the worktree list,
the landing records, your drives, and the background jobs — and answers ten questions with real
numbers:

- **Did finished work reach master?** Every branch marked ready in the last 24 hours either landed,
  or was handed back to you *with a card in your inbox*. A branch that did neither is the one thing
  this exists to catch: work that quietly went nowhere.
- **Is anything stuck?** Nothing sitting in the landing queue for more than 30 minutes with no result.
- **Is there room on the disks?** Every drive that holds worktrees is above its free-space floor, and
  no worktree is being created on the same drive as the code itself.
- **Are old worktrees being cleaned up?** Finished ones get their folder back promptly, and abandoned
  ones are archived within a day. The count is stated out loud **even when it's zero** — a pile
  nobody counts is how this once grew to several hundred.
- **Are the cleanup jobs running?** The lander, the cleaner, the trash sweeper, the archiver, the
  reclaimer and the checkout keeper are all present and have each run recently.
- **Did a cleanup pass actually happen — every day?** There is a real recorded cleanup run on each
  of the last three days. This is deliberately a second, separate question from the one above: a job
  can tick along healthily forever while deciding "not due yet" or "held for load" and never
  actually clean anything. That happened — a whole day with zero cleanup runs while the app ran
  continuously, and it read exactly like an ordinary quiet day. Now it goes red.
- **Is every landing traceable?** Yesterday's landings each kept enough detail to answer "how long
  did that take" days later.
- **Is the main copy of the code clean?** On the main branch, up to date, and matching what was
  actually committed — including nothing missing from disk.
- **Is anything sitting half-finished?** A worktree that is still *in progress* — not finished, not
  abandoned, just stuck — is reported with the reason it is stuck and how long it has been that way.
  This is a separate question from "are old worktrees being cleaned up" on purpose, and the
  difference is the whole point: that one watches the queues that already have a cleaner behind
  them, and a draining queue was masking a pile of frozen work. Four different things can stick, and
  each needs a different fix — a setup that never finished, a worktree whose owning session is gone,
  one held far too long by a session that is still alive, and one marked ready to merge that never
  landed. Only that last one is treated as needing you; the rest are the machine being behind.
  Anything you have pinned is never reported, at any age.
- **Is the close-out watcher alive?** Once an hour a read-only pass looks at every open worktree
  record and writes down what the lifecycle *would* do with it — return the folder, keep the branch
  for the landing queue, hold it, or mark it stuck — beside what today's cleanup would do, without
  touching anything. It leaves a receipt before and after each pass. This question is red when a
  repo has no receipt inside two hours, when a pass started and never finished, or when the last
  pass failed. What the pass *found* — how many records are undecided, the oldest one, how many
  branches are past a week with nobody coming back — is carried along as detail, not as a verdict.

**A check it cannot run counts as a failure**, never a pass. "We found no problems" and "we couldn't
look" are different answers, and only one of them is good news.

**What you see.** Nothing, on a day everything worked — the result is filed quietly. When something
is wrong you get **one card, once a day**, opening with *Needs a look* and a count. And if the check
**didn't run at all** (usually because the app was closed), you get a **red card** saying so at the
next opportunity — a missing day is never silent.

**Asking any time.** You don't have to wait for the daily run:

- In a terminal: `npm run lifecycle:verify` (add `-- --date 2026-09-03` for an earlier day).
- Over the local API: `GET /lifecycle/verify` on `127.0.0.1:19519`.
- For the close-out watcher on its own: `npm run lifecycle:leftovers` — whether it is alive, what it
  would do with the current records beside what today's cleanup does, the whole worktree population
  with every count shown against its total, and any open discrepancies. Add `--json` for the raw
  model, or `--refs` to also count git refs by namespace (that listing runs only when you ask).

Every run leaves a detailed record kept for **30 days**, including the specific branch and folder
names behind any problem — those stay out of the readable summary on purpose, and live in the record
where they are useful for digging in later.

**It also decides whether to spend anything.** Because the check is free and thorough, the paid
maintenance session now runs **only when something genuinely needs a person's judgement** — a real
merge conflict, a stuck branch, a checkout somebody has to look at. A queue that is merely draining
slowly is a machine being behind, not a decision, and no longer buys a session. If the check itself
can't run, the session goes ahead anyway, so a blind spot can never quietly cancel your maintenance.

### Working with the auto-lander

The auto-lander takes **two steps**, not one — the master switch alone lands nothing:

1. Switch it on in the **Dev Pipeline panel → Setup tab** (*Auto-land ready branches*).
2. **Add each repo to its list and take that repo out of observe-only.** Enrolment is opt-in per repo and unlisted means OFF: a repo with no entry is never watched, and a repo left in observe-only (dry-run) watches without ever moving the ref.

With both done, the auto-lander runs continuously and lands every branch that merges cleanly the moment it's tagged — the easy majority, on its own, with nothing else moving your branch alongside it.

Some branches it cannot finish unattended and hands back instead:

- branches it **handed back** because they hit a merge conflict (it never force-merges — it marks them for rework instead),
- branches it's **stuck on** (a tag whose commit has moved on, or a temporary repository snag),
- and anything still waiting when the auto-lander is **off** or this repo isn't enrolled.

**The daily maintenance run reports these; it does not sweep them.** Clearing them is a run *you*
start — the **merge-all-ready** skill, from a session you are watching. Nothing unattended may act
as a second mover: while the lander is armed, `land:catch-up` refuses outright unless a person
passes an explicit override, and the daily run's own instructions forbid every lander by name. A
long queue with the lander switched off is something for you to look at, not a cue for the machine
to start merging.

### Safety

- **Unattended by design, and unattended never lands.** With no human watching, worktree-cleanup removes only provably-merged worktrees, and the daily run only *reports* the landing queue. When you run merge-all-ready yourself it lands only what merges cleanly or resolves safely — anything ambiguous is left untouched and reported.
- **Held, never forced, while the computer is busy.** A cleanup the nightly session asks for through the control server while the machine is under load is held until it calms (the request answers `deferred` and spawns nothing), and the session reports that as "held for load" rather than a failure; only a person can push the automated cleanup itself through — your **Run cleanup now** button, or your own token from a terminal. See [worktree-cleanup-skill.md](worktree-cleanup-skill.md#why-a-cleanup-request-can-be-held). While it waits, the session covers for the hold itself — it clears the same kind of already-finished worktrees one at a time through the same safe, reversible mechanism, so a busy stretch doesn't let the backlog pile up untouched until someone notices.
- **It reads the whole backlog, not the top of it.** The session asks the worktree list for its candidates in pages and keeps going until the list says there is nothing left, rather than trusting a single request to have returned everything. This matters more than it sounds: that request returns the most recently updated slice by default, so on a busy repo a one-request sweep sees only the newest handful of finished worktrees and reports a clean sweep while the older ones sit there — which is how a backlog quietly grows during runs that all looked successful. If a run ever reports finishing while the count of registered worktrees has not moved, that is the thing to look at.
- **Local only.** merge-all-ready never pushes and never touches any remote; landing means advancing your *local* integration branch.
- **Never loses work.** Neither skill discards uncommitted changes or unmerged commits; when in doubt, they keep and report.
- **One mover at a time.** The auto-lander is the only thing that lands unattended. A merge-all-ready run you start yourself shares the same atomic, local-only landing floor and never fights a branch the lander is mid-rescue on — but no scheduled or automatic job is allowed to land alongside it.

### Turning it off

Turn off the daily run in the Dev Pipeline panel's Setup tab while keeping the skills installed (so you can still run them by hand), or turn off the whole Dev Pipeline to remove both skills.

## Related

[Dev Pipeline](dev-pipeline.md) is the workflow these jobs ride with, and the [Dev Pipeline panel](dev-pipeline-panel.md) is where its controls live.
