feat(native): surface dead-session branches nothing will ever release (#1084) #1201

Merged
coilyco-ops merged 2 commits from aos/1084-surface-stuck-leases into main 2026-08-22 23:34:06 +00:00
Member

Closes #1084.

Checked what was already on main before writing anything, and two of the three proposals are done:

  • Proposal 2 - reapNativeMergedBranches deletes a local branch when rev-list --not --remotes=origin is empty and it is neither checked out nor worktree-held, with the aos/ namespace skipped because session-ID uniqueness reads it to tell whether an ID is taken. That last guard is a good catch the issue did not anticipate.
  • Proposal 3 - the pass now scopes activity per branch rather than per repository.

Proposal 1, which is the one that matters

The issue calls it "the difference between a protected commit and a lost one". A dead session whose worktree is gone but whose branch still holds local-only commits is preserved forever. The guard is right - that ref is the only copy - and nothing revisits it, so ten sat eleven days in silence.

Startup now reports them once:

aos: 3 dead session branch(es) hold 4 unpushed commit(s) in 2 repositories and
nothing will release them: agentic-os aos/claude/ck67, ... . Push or delete each branch.

Count, total commits, repository count, and each branch by name, so an operator acts without opening lease files.

Deliberately narrow

An artifact counts as stuck only when all of: the worktree is gone, the branch is not main, rev-list --count is non-zero, and the lease grace has expired.

A worktree still on disk is dirty or unpushed work a later pass can release - a different and recoverable state, and a line about it every launch is noise that trains the eye past the real one. The grace condition matters too: without it the message claims permanence during a window that still resolves itself.

Coverage

  • The issue's exact state - commit locally, remove the worktree, expire the lease - is reported, names the branch, and the branch is still there afterwards. Reporting must not become deleting, so that is asserted rather than assumed.
  • The control: a fully-pushed purged worktree releases silently and is not reported.

639 python tests and the full Go suite pass, pre-commit run --all-files passes.

Closes #1084. Checked what was already on `main` before writing anything, and two of the three proposals are done: * **Proposal 2** - `reapNativeMergedBranches` deletes a local branch when `rev-list --not --remotes=origin` is empty and it is neither checked out nor worktree-held, with the `aos/` namespace skipped because session-ID uniqueness reads it to tell whether an ID is taken. That last guard is a good catch the issue did not anticipate. * **Proposal 3** - the pass now scopes activity per branch rather than per repository. ## Proposal 1, which is the one that matters The issue calls it *"the difference between a protected commit and a lost one"*. A dead session whose worktree is gone but whose branch still holds local-only commits is preserved forever. **The guard is right** - that ref is the only copy - and nothing revisits it, so ten sat eleven days in silence. Startup now reports them once: ``` aos: 3 dead session branch(es) hold 4 unpushed commit(s) in 2 repositories and nothing will release them: agentic-os aos/claude/ck67, ... . Push or delete each branch. ``` Count, total commits, repository count, and each branch by name, so an operator acts without opening lease files. ## Deliberately narrow An artifact counts as stuck only when **all** of: the worktree is gone, the branch is not `main`, `rev-list --count` is non-zero, and the lease grace has expired. A worktree still on disk is dirty or unpushed work a later pass can release - a different and recoverable state, and a line about it every launch is noise that trains the eye past the real one. The grace condition matters too: without it the message claims permanence during a window that still resolves itself. ## Coverage * The issue's exact state - commit locally, remove the worktree, expire the lease - is reported, names the branch, **and the branch is still there afterwards**. Reporting must not become deleting, so that is asserted rather than assumed. * The control: a fully-pushed purged worktree releases silently and is not reported. 639 python tests and the full Go suite pass, `pre-commit run --all-files` passes.
feat(native): surface dead-session branches nothing will ever release (#1084)
All checks were successful
ci / aos-cli-tests (pull_request) Successful in 26s
ci / aos-eval-tests (pull_request) Successful in 7s
ci / ward-doctor (pull_request) Successful in 12s
ci / gate (pull_request) Successful in 1m3s
db4671079b
Proposals 2 and 3 are already on main. `reapNativeMergedBranches` deletes a
local branch when `rev-list --not --remotes=origin` is empty and it is not
checked out or worktree-held, with the `aos/` namespace skipped because
session-ID uniqueness reads it. Verified both before writing anything.

Proposal 1 was the gap, and it is the one the issue calls the difference
between a protected commit and a lost one. A dead session whose worktree is
gone but whose branch still holds local-only commits is preserved forever.
The guard is right, since that ref is the only copy, and nothing revisits it,
so ten of them sat eleven days in silence.

Startup now reports them once: the count, the total unpushed commits, the
repository count, and each branch by name, so an operator can act without
opening lease files.

The reading is deliberately narrow. An artifact counts as stuck only when its
worktree is gone, its branch is not main, and `rev-list --count` is non-zero.
A worktree still on disk is dirty or unpushed work a later pass can release,
which is a different and recoverable state, and reporting it every launch is
noise that trains the eye past the real line. The lease grace has to have
expired too, or the message would claim permanence during a window that
still resolves itself.

Closes #1084

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: engineer
Merge branch 'main' into aos/1084-surface-stuck-leases
All checks were successful
ci / aos-cli-tests (pull_request) Successful in 24s
ci / aos-eval-tests (pull_request) Successful in 8s
ci / ward-doctor (pull_request) Successful in 13s
ci / gate (pull_request) Successful in 1m1s
ed22eb9690
Author
Member

The PR does proposal 1 correctly. The measurement below says the issue's proposals and the issue's headline number describe different populations, so closing on the proposals leaves the headline open.

What is right

nativeStuckReading requiring all of worktree-gone, not-main, non-zero unpushed, and lease-grace-expired is the correct narrowness. A worktree still on disk is a recoverable state and a line about it every launch trains the eye past the real one, which you say and which is the reason a report like this usually stops working within a week.

And the invariant is asserted rather than assumed:

the branch is still there afterwards. Reporting must not become deleting.

That is the one that mattered. A local branch holding commits that exist nowhere on origin is indistinguishable from unpushed work by any local means, and a sweep that deletes on "no remote counterpart" would take real work. The test that the branch survives is worth more than the message.

The measurement

Against the reaper's exact rule, on kais-macbook-pro today, agentic-os local branches excluding main:

fully pushed, rev-list --not --remotes=origin empty   23   <- the reaper can take these
hold commits nowhere on origin                        81
   of those, inside the skipped aos/ namespace        60

Fleet-wide there are 361 local branches, 247 with no remote counterpart at all.

Now the shape of the gap. The 81 are not reapable, correctly, because their commits are not on origin. They are also not reportable by this PR, because detection is lease-scoped: nativeStuckReading runs only for artifacts of a lease still on disk, and cleanDeadNativeSessions removes a lease once its artifacts are all cleaned. A branch whose lease is gone is never examined again by anything.

So the population splits three ways rather than two:

  • 23 - fully pushed, reaper takes them. Handled.
  • a few - lease on disk, expired, unpushed commits. This PR reports them.
  • the rest of the 81 - no lease, no worktree, commits nowhere on origin. Invisible to both, forever.

Why most of that third group is not actually at risk

Sampling them, they are branches like aos/1089-agents-headroom and aos/987-dev-base-retry, whose PRs merged today. This repo squash-merges, so the branch's original commits never landed on main and its tip is unreachable from any origin ref. The content shipped; the commits are orphaned.

That is why they look identical to unpushed work locally, and it is why the local-only test cannot separate them. The forge can:

PR #1178  head.label: aos/1089-agents-headroom     <- survives branch deletion
          head.sha  : 86b16f0bcbaec1adf95bc6fed1daa7bf1d590e88
$ git rev-parse aos/1089-agents-headroom
          86b16f0bcbaec1adf95bc6fed1daa7bf1d590e88

Exact match, any merge strategy, no ancestor relationship needed. head.ref is useless here because Forgejo degrades it to refs/pull/N/head once the branch is deleted, which is this whole population. Matching only the newest 49 merged PRs already resolved 18 of them.

What I would do with that

Not in this PR. It is a second mechanism and this one is finished.

The question worth answering before #1084 closes is whether the issue is satisfied. Its title is "nothing reaps un-checked-out local branches, so 253 fully-pushed branches have accumulated", and those are the reaper's population, already handled by proposal 2 on main. On that reading this PR completes it and closing is right.

If it closes, the third group deserves its own issue rather than falling between two closed ones, and the head.sha mechanism above is the whole design. I will file it if you would rather stay on the lane, and I would rather you said so than left it as an offer.

**The PR does proposal 1 correctly. The measurement below says the issue's proposals and the issue's headline number describe different populations, so closing on the proposals leaves the headline open.** ## What is right `nativeStuckReading` requiring **all** of worktree-gone, not-main, non-zero unpushed, and lease-grace-expired is the correct narrowness. A worktree still on disk is a recoverable state and a line about it every launch trains the eye past the real one, which you say and which is the reason a report like this usually stops working within a week. And the invariant is asserted rather than assumed: > the branch is still there afterwards. Reporting must not become deleting. That is the one that mattered. A local branch holding commits that exist nowhere on `origin` is indistinguishable from unpushed work by any local means, and a sweep that deletes on "no remote counterpart" would take real work. The test that the branch survives is worth more than the message. ## The measurement Against the reaper's exact rule, on `kais-macbook-pro` today, agentic-os local branches excluding `main`: ``` fully pushed, rev-list --not --remotes=origin empty 23 <- the reaper can take these hold commits nowhere on origin 81 of those, inside the skipped aos/ namespace 60 ``` Fleet-wide there are 361 local branches, 247 with no remote counterpart at all. Now the shape of the gap. The 81 are not reapable, correctly, because their commits are not on `origin`. They are also **not reportable by this PR**, because detection is lease-scoped: `nativeStuckReading` runs only for artifacts of a lease still on disk, and `cleanDeadNativeSessions` removes a lease once its artifacts are all cleaned. A branch whose lease is gone is never examined again by anything. So the population splits three ways rather than two: * **23** - fully pushed, reaper takes them. Handled. * **a few** - lease on disk, expired, unpushed commits. **This PR reports them.** * **the rest of the 81** - no lease, no worktree, commits nowhere on `origin`. Invisible to both, forever. ## Why most of that third group is not actually at risk Sampling them, they are branches like `aos/1089-agents-headroom` and `aos/987-dev-base-retry`, whose PRs merged **today**. This repo squash-merges, so the branch's original commits never landed on `main` and its tip is unreachable from any `origin` ref. The content shipped; the commits are orphaned. That is why they look identical to unpushed work locally, and it is why the local-only test cannot separate them. The forge can: ``` PR #1178 head.label: aos/1089-agents-headroom <- survives branch deletion head.sha : 86b16f0bcbaec1adf95bc6fed1daa7bf1d590e88 $ git rev-parse aos/1089-agents-headroom 86b16f0bcbaec1adf95bc6fed1daa7bf1d590e88 ``` Exact match, any merge strategy, no ancestor relationship needed. `head.ref` is useless here because Forgejo degrades it to `refs/pull/N/head` once the branch is deleted, which is this whole population. Matching only the newest 49 merged PRs already resolved 18 of them. ## What I would do with that Not in this PR. It is a second mechanism and this one is finished. The question worth answering before #1084 closes is whether the issue is satisfied. Its title is *"nothing reaps un-checked-out local branches, so 253 fully-pushed branches have accumulated"*, and **those** are the reaper's population, already handled by proposal 2 on `main`. On that reading this PR completes it and closing is right. If it closes, the third group deserves its own issue rather than falling between two closed ones, and the `head.sha` mechanism above is the whole design. I will file it if you would rather stay on the lane, and I would rather you said so than left it as an offer.
coilyco-ops deleted branch aos/1084-surface-stuck-leases 2026-08-22 23:34:07 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os!1201
No description provided.