Skip to content

feat(pr-pipeline): delete finished branches daily, keeping each while its issue is open - #8976

Merged
kyle-sexton merged 7 commits into
mainfrom
feat/branch-cleanup-issue-lifetime
Oct 22, 2026
Merged

kyle-sexton merged 7 commits into
mainfrom
feat/branch-cleanup-issue-lifetime

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Refs: #8958

Summary

This is the single W-7 branch-cleanup PR and supersedes #8959. A daily job, maintenance-delete-stale-branches.yml, deletes the branches whose work is finished. Owner decision (2026-10-22, #8958): no dates. A branch lives as long as its issue is open and is deleted once that issue closes.

Fix

  • Job. The job lives in .github/workflows/maintenance-delete-stale-branches.yml, with its rules and tests in .github/scripts/branch-cleanup/. It is REST only, runs no model, and checks out no branch code. Its token can write contents and read issues and pull requests. The lanes kill switch stops it. The schedule deletes; a manual run is a dry-run unless set otherwise, and in dry-run the client refuses every request but GET.
  • Never touched. The default branch, and branches under release/*, dependabot/* and gh-readonly-queue/*.
  • Kept. A protected branch or one a ruleset applies to; a branch with an open pull request; a lane branch while its issue is open; any other branch while an issue its newest pull request links is open.
  • For a person. A non-lane branch that never had a pull request; a merged branch with commits ahead of the default branch; a closed-unmerged branch whose tip moved; a branch whose read failed.
  • Deleted. A branch whose pull request is closed or merged and whose linked issues are all closed, or that links no issue; also a lane branch with no pull request whose issue closed. A lanes-App stop or resume marker no longer keeps a branch once its issue is closed. There is no min-age-days key.
  • Per-run safety. A run deletes at most max-deletes-per-run (default 25), in listing order. Before each delete the job re-reads the branch tip and the open pull requests from and into the branch. Every delete appends {repo, branch, sha, rule, issue, pr, deletedAt} to the step summary and to the ledger artifact.
  • Call budget (fixes Codex's P2 on feat(pr-pipeline): delete stale branches daily by the branch-cleanup rule #8959). Before evaluating any branch, the job reserves 4 calls per planned delete from the 600-call budget. It evaluates with the rest and stops once it has found max-deletes-per-run candidates or spent that share; the branches after that wait for the next run. Before this fix, about 300 deletable branches used up the whole budget during evaluation, so every planned delete failed its re-read and nothing was deleted.
  • Config. The branch-cleanup leaf (max-deletes-per-run) is in pr-pipeline.yaml and its schema. branchCleanupFromBase is in resolve-config. The README has a "Branch cleanup" section. There is a changelog fragment under .changes/pr-pipeline/.

Still open from #8958: item 1 (atomic compare-and-delete) and item 2 (split plan from delete). That is why this PR uses Refs:.

Verification

  • node --test in .github/scripts/branch-cleanup/: 20 pass, 0 fail. The new budget test uses the real client() over a stubbed fetch. It covers 300 deletable branches under a 600-call budget, and the run deletes 25. Before the fix it failed: 0 deleted. The tests for issue lifetime failed on the pre-change rules: a stopped run's branch is kept while its issue is open and deleted once it closes, and a closed PR branch with no linked issue is deleted.

  • node --test in .github/actions/resolve-config/: 551 pass, 0 fail.

  • scripts/check-affected.sh: 9 passed, 4 skipped, 0 failed.

  • Live dry run: not run by the implementer. The owner should run this and paste the summary here:

    env -C <worktree> GITHUB_TOKEN="$(gh auth token)" REPOSITORY=melodic-software/claude-code-plugins DRY_RUN=true BASE_PATH=<worktree> node .github/scripts/branch-cleanup/cleanup.mjs

Related

🤖 Generated with Claude Code

kyle-sexton and others added 6 commits October 21, 2026 20:42
…rule

Owner decision W-7: no non-default branch lives forever. A daily
maintenance-delete-stale-branches.yml sorts every branch: closed or merged
PR and closed linked issue at least min-age-days (14) ago deletes, at most
max-deletes-per-run (25) a run; an open issue, an open PR, a protected or
ruleset branch, and a stopped lane run's branch are never deleted; anything
unreadable or without a PR is listed for a person. Each delete re-reads the
tip and open PRs and writes a ledger line first. Manual runs default to
dry-run, which refuses every non-GET request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p suite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner decision on #8958: no dates. A branch lives as long as its issue
is open and is deleted once the issue closes. Remove the min-age-days key
and every age check, and the stopped-run keep: a lane branch whose issue
closed and that has no open pull request is deletable whatever stop or
resume marker the lanes App left. The cap now takes branches in listing
order. Everything else holds: open issue or open pull request keeps,
merged-with-commits-ahead and never-had-a-PR branches go to a person,
exclusions, cap, kill switch, dry-run and ledger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evaluation spent the shared 600-call budget, so with about 300 deletable
branches every planned delete failed its re-read and nothing was ever
deleted (Codex review on #8959). cleanup.mjs now reserves 4 calls per
planned delete from the client's budget, evaluates with the rest, and
stops evaluating once it has found max-deletes-per-run candidates or
spent its share; the remaining branches wait for the next run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton changed the base branch from feat/branch-cleanup-w7 to main October 22, 2026 01:49
@kyle-sexton kyle-sexton changed the title feat(pr-pipeline): keep a branch only while its issue is open feat(pr-pipeline): delete finished branches daily, keeping each while its issue is open Oct 22, 2026
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 22, 2026 01:53
@kyle-sexton
kyle-sexton enabled auto-merge October 22, 2026 01:53
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T01:55:11.550474Z 9c20bb6 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 6c1af60ab297afe536c22f9df65d3a0b658777b0: 0 finding(s), 0 as inline comments.

Security review (incremental, 9c20bb6 → 6c1af60): no security issues found.

Files read: cleanup.mjs, cleanup.test.mjs, and docs/conventions/pr-pipeline/README.md, from .claude-lane/incremental.diff.

  • Change. cleanup.mjs now rotates the start of the branch list by GITHUB_RUN_NUMBER × stride, modulo the list length. Evaluation stops at the cap or call budget as before, and unevaluated counts against the rotated list.
  • Input trust. GITHUB_RUN_NUMBER is set by the Actions runner, not by a PR author or branch name. It is parsed with parseInt and falls back to 0 on a non-numeric value. The start offset is taken modulo branches.length, with an empty-list guard, so it cannot go out of range or produce a negative index.
  • Delete path. The rotation does not change which branches are deletable or how that is decided. Verdicts, the max-deletes-per-run cap, the call-budget reservation and the delete-time re-read of tip and open PRs are all untouched. Only the evaluation order changes, so no kept or protected branch becomes deletable.
  • Tests and docs. The new test mocks fetch and does not touch credentials. The README change is prose only.
  • Manifests. No plugin manifest changed, so no manifest <field> lines apply.
  • Instruction surfaces. No standing instruction was removed or softened. The README edit replaces "wait for the next run" with the rotation description.

Comment thread .github/scripts/branch-cleanup/cleanup.mjs
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 6c1af60ab297afe536c22f9df65d3a0b658777b0: 0 finding(s), 0 as inline comments.

Code review (incremental, 9c20bb6 → 6c1af60)

No findings. Reviewed cleanup.mjs, cleanup.test.mjs and the pr-pipeline README change against the PR context. Refs: #8958 closes no issue, so there were no acceptance criteria to check.

Change reviewed: the run now starts at (GITHUB_RUN_NUMBER × stride) % branches.length and wraps, with stride = floor(evalLimit / 4). This stops kept branches early in the listing from using up the evaluation share on every run.

Checked by reading the code (cleanup.mjs:141-187):

  • No run-number case: with GITHUB_RUN_NUMBER unset or non-numeric, start is 0 and the old behavior is unchanged. With an unlimited budget, stride is 0 and nothing rotates.
  • Runner env: the workflow does not set GITHUB_RUN_NUMBER, but the runner provides it, and main() passes process.env. The workflow's env: block is unchanged.
  • Coverage per run: a run reads roughly evalLimit / 3 or more branches, since a non-deletable branch costs at most about three reads. That is more than evalLimit / 4, so consecutive starts leave no gap. A branch with several linked issues costs more, and I did not measure how often that happens.
  • Mid-run changes: unevaluated is now computed against ordered, so the summary count stays right. Deleting branches between runs changes branches.length, which only moves the start point.
  • Test setup: the new test has 493 kept branches (two reads each) ahead of 8 deletable ones. The test setup is consistent with the code: the 8 deletable branches fall inside the windows of runs 2 through 5.

Not verified: I could not run node --test here because the sandbox denied it, so I did not confirm that the new test passes. The PR's claim of 20 passing tests predates this commit and is author-claimed.

Minor, not blocking: the README line 1603 still says the job deletes "in listing order", though the order now starts at a rotated point. The same paragraph explains the rotation, so it is not misleading.

claude-lane: claude-review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c20bb61f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/branch-cleanup/cleanup.mjs
Evaluation always started at the head of the branch listing, so kept or
reported branches early in it spent the evaluation share every run and
the later branches were never read. Each run now starts at
GITHUB_RUN_NUMBER times a quarter of the evaluation share, modulo the
listing length, wrapping around, so successive runs cover the whole
listing. The summary says the next run starts elsewhere instead of
claiming it continues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 39916bd Oct 22, 2026
39 of 49 checks passed
@kyle-sexton
kyle-sexton deleted the feat/branch-cleanup-issue-lifetime branch October 22, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant